I'm trying to make an HTTPS connection to a server that has a certificate set to expire in April 2013 and uses GlobalSign as the root certificate.
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
// urlConnection.setSSLSocketFactory(sslSocketFactory);
urlConnection.setDoOutput(true);
urlConnection.setChunkedStreamingMode(0);
// Send the POST data
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
out.write(postParamString.toString().getBytes("UTF8"));
// Read the reply
InputStream in = urlConnection.getInputStream();
As it stands, this throws javax.net.ssl.SSLHandshakeException: org.bouncycastle.jce.exception.ExtCertPathValidatorException: Could not validate certificate signature.
when getOutputStream()
is called.
This same site and certificate are valid in the stock HTC web browser and desktop browsers. When I use the same code to access Google it works (but then complains about a 404 error). Various posts on StackOverflow imply that it should "just work" and others say to set up your own key store (or disable all HTTPS validation!) I assume the difference in behaviour is down to different root key stores in use (Can anyone clarify this?).
I've now tried creating a key store using bouncy castle but I can't get this to load on my device.
After exporting the certificate from Firefox, I create a key store using:
keytool.exe -import -alias onlinescoutmanager -file www.onlinescoutmanager.co.uk.crt -storetype BKS -keystore res\raw\keystore
This is then loaded and used in the application using:
InputStream stream = context.getResources().openRawResource(R.raw.keystore);
// BKS seems to be the default but we want to be explicit
KeyStore ks = KeyStore.getInstance("BKS");
ks.load(stream, "www.onlinescoutmanager.co.uk".toCharArray());
stream.close();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
SSLContext context2 = SSLContext.getInstance("TLS");
context2.init(null, new TrustManager[] { defaultTrustManager }, null);
sslSocketFactory = context2.getSocketFactory();
This is failing with java.io.IOException: Wrong version of key store.
when keystore.Load()
is called.
I have ensured I'm passing -storetype BKS
, used a <=7 character keystore password, added the CA certs to the key store, and using both Bouncy Castle version 1.45 and 1.47 to create the key store with no change in the reported error message.
My environment is Eclipse Juno 4.2.1 with JRE 1.7u9b5 running on Windows 8. The device I'm testing on is an HTC sensation running stock Android 2.3. The application has a minimum SDK version of 7 and a target of 15.
If anyone can explain how to create a valid BKS key store on Windows 8 or how I can get Java to use the same key store as the browser(or system?) that would be appreciated.
You can download the entire project as it was at the time of writing, and the generated keystore if required.
Thanks to various people for their hints on this, there are multiple things that all needed to be correct for it to work.
If the HTTPS site's certificate is signed by a trusted root certificate then it will work out of the box without a custom SSLSocketFactory
.
The trusted root certificates CAN be different to that used by a browser so don't assume that if it works in the Android web browser then it will work in your app.
If it's not a trusted root certificate and you get exceptions like javax.net.ssl.SSLHandshakeException: org.bouncycastle.jce.exception.ExtCertPathValidatorException: Could not validate certificate signature.
, then you need to create and load a key store as below.
The key store needs to be generated using the Bouncy Castle provider (1) by specifying -storetype bks
on the keytool
command line.
If Bouncy Castle is not installed correctly then this will fail with various exceptions including java.security.KeyStoreException: BKS not found
.
If the key store is not created with the Bouncy Castle provider then you may get the java.io.IOException: Wrong version of key store.
exception, causing confusion with the next case.
You need to use an appropriate version (1, 2, 3) of the Bouncy Castle provider. In most cases, this seems to be version 1.46.
This can be put into your JRE's lib/ext/
folder and the class name added to lib/security/java.security
, or specified directly on the command line to keytool
.
If it's an incompatible version (or store type) you will get exceptions along the lines of java.io.IOException: Wrong version of key store.
again.
You must include all intermediary and the root certificate. If any are missing, you will get a javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
exception.
The certificate chain MUST be in order for them to be validated correctly. If they aren't you will either get javax.net.ssl.SSLHandshakeException: org.bouncycastle.jce.exception.ExtCertPathValidatorException: IssuerName(CN=XYZ) does not match SubjectName(CN=ABC) of signing certificate.
or again, a generic javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
I have not found a way to order them in the key store so resorted to doing it in code at runtime.
Some people have suggested that using a keystore password longer than 7 characters will also cause it to fail, but that's not what I've found.
I think this covers every pitfall I found, but feel free to expand and add linsk to related questions.