I have an app communicating with a HTTPS RPC.
The HTTP server is using a CAcert signed certificate.
I'm using a custom TrustManager for validating the certificate.
- Because I can not be sure, CAcert is included in all devices' trusted key store.
- Because I want to allow only CAcert to sign a certificate for this connection.
However, I'm following Google's best practices. The only thing I changed is:
- Load the CAcert root certificate from a static byte[] instead a file
- Replace the last part, where the example code loads a file, with
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
. There is a JSONRPC2 API on top of the UrlConnection.
Devices tested:
- working on Nexus 4 / mako running API18 / CM10.2
- working on API18 emulator
- working on API17 emulator
- working on API14 emulator
- not working on a HTC G2 running API10 / CM7.*
- not working on API8 emulator
On low API devices it fails verifying the certificate during SSL handshake.
When trying to load https://google.com
with this TrustManager
on API18, it fails as expected because no trust anchor could be found.
So basically, this code should work and all of the methods are API1...
I know, that UrlConnection was broken on some lower APIs.
How do I fix this?
Code:
/**
* Trust only CAcert's CA. CA cert is injected as byte[]. Following best practices from
* https://developer.android.com/training/articles/security-ssl.html#UnknownCa
*/
private static void trustCAcert() {
try {
// Load CAs from an InputStream
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteArrayInputStream is = new ByteArrayInputStream(CACERTROOTDER);
Certificate ca;
try {
ca = cf.generateCertificate(is);
Log.d(TAG, "ca=", ((X509Certificate) ca).getSubjectDN());
} finally {
is.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
HttpsURLConnection.setDefaultSSLSocketFactory(
sslContext.getSocketFactory());
// added for testing only
URL u = new URL(
"https://myremoteapiurlsignedwiththesamecert.com/v1/doc.html");
HttpsURLConnection con = (HttpsURLConnection) u.openConnection();
con.setSSLSocketFactory(sslContext.getSocketFactory());
BufferedReader r = new BufferedReader(
new InputStreamReader(
con.getInputStream())); // the exception is thrown here
// because verification fails
String l;
while ((l = r.readLine()) != null) {
Log.d(TAG, "l: ", l);
}
} catch (IOException e) { // none of the exceptions is thrown during setup
Log.e(TAG, "IOException", e);
} catch (CertificateException e) {
Log.e(TAG, "CertificateException", e);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException", e);
} catch (KeyStoreException e) {
Log.e(TAG, "KeyStoreException", e);
} catch (KeyManagementException e) {
Log.e(TAG, "KeyManagementException", e);
}
}
Logs:
APIUtils D ca=OID.1.2.840.113549.1.9.1=#1612737570706F7274406361636572742E6F7267, CN=CA Cert Signing Authority, OU=http://www.cacert.org, O=Root CA
E IOException
E javax.net.ssl.SSLException: Not trusted server certificate
E at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:371)
E at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.getSecureSocket(HttpConnection.java:168)
E at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:399)
E at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:1152)
E at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:253)
E at de.terminbuddy.android.network.APIUtils.trustCAcert(APIUtils.java:294)
E at de.terminbuddy.android.network.APIUtils.initRpcSession(APIUtils.java:243)
E at de.terminbuddy.android.network.APIUtils.runRPC(APIUtils.java:323)
E at de.terminbuddy.android.network.AsyncJSONRPCTask.doInBackground(AsyncJSONRPCTask.java:55)
E at de.terminbuddy.android.network.AsyncJSONRPCTask.doInBackground(AsyncJSONRPCTask.java:17)
E at android.os.AsyncTask$2.call(AsyncTask.java:185)
E at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
E at java.util.concurrent.FutureTask.run(FutureTask.java:137)
E at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
E at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
E at java.lang.Thread.run(Thread.java:1096)
E Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Could not validate certificate signature.
E at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:168)
E at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:366)
E ... 15 more
E Caused by: java.security.cert.CertPathValidatorException: Could not validate certificate signature.
E at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi.engineValidate(PKIXCertPathValidatorSpi.java:342)
E at java.security.cert.CertPathValidator.validate(CertPathValidator.java:202)
E at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:164)
E ... 16 more
E Caused by: java.security.SignatureException: Signature was not verified.
E at org.apache.harmony.security.provider.cert.X509CertImpl.fastVerify(X509CertImpl.java:601)
E at org.apache.harmony.security.provider.cert.X509CertImpl.verify(X509CertImpl.java:544)
E at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi.engineValidate(PKIXCertPathValidatorSpi.java:337)
E ... 18 more