title: SSL TrustManager setup on lower Android APIs
body: <p>I have an app communicating with a HTTPS RPC.<br>
  The HTTP server is using a CAcert signed certificate.</p>
  
  <p>I'm using a custom TrustManager for validating the certificate.</p>
  
  <ul>
  <li>Because I can not be sure, CAcert is included in all devices' trusted key store.</li>
  <li>Because I want to allow only CAcert to sign a certificate for this connection.</li>
  </ul>
  
  <p>However, I'm following Google's <a href="https://developer.android.com/training/articles/security-ssl.html#UnknownCa" rel="nofollow">best practices</a>.
  The only thing I changed is:</p>
  
  <ul>
  <li>Load the CAcert root certificate from a static byte[] instead a file</li>
  <li>Replace the last part, where the example code loads a file, with <code>HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());</code>. There is a JSONRPC2 API on top of the UrlConnection.</li>
  </ul>
  
  <p>Devices tested:</p>
  
  <ul>
  <li>working on Nexus 4 / mako running API18 / CM10.2</li>
  <li>working on API18 emulator</li>
  <li>working on API17 emulator</li>
  <li>working on API14 emulator</li>
  <li><strong>not working</strong> on a HTC G2 running API10 / CM7.* </li>
  <li><strong>not working</strong> on API8 emulator</li>
  </ul>
  
  <p>On low API devices it fails verifying the certificate during SSL handshake.<br>
  When trying to load <code>https://google.com</code> with this <code>TrustManager</code> on API18, it fails as expected because no trust anchor could be found.<br>
  So basically, this code should work and all of the methods are API1...<br>
  I know, that UrlConnection was broken on some lower APIs.</p>
  
  <p>How do I fix this?</p>
  
  <p>Code:</p>
  
  <pre><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);
      }
  }
  </code></pre>
  
  <p>Logs:</p>
  
  <pre><code>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
  </code></pre>