Overriding the SSL Trust Manager in Android
Asked Answered
N

2

6

I am trying to override the trust manager in Android. I want to let the underlying trust manager check certificates but I need to determine if a certificate is expired. If the certificate is expired, I need to ignore it and accept the certificate. Some mobile devices will reset the date to an old date if the battery is removed, causing the certificate to appear as though it expired. My app must continue to keep running even if this happens.

The problem I am having is that this line of code throws a NullPointerException:

origTrustmanager.checkServerTrusted(certs, authType);

According to the docs, checkServerTrusted should never throw a NullPointerExeption. certs has two items in it. authType is set to "RSA". If I don't implement a custom Trust Manager, an exception will be thrown that clearly indicates that the certificate has expired, so I know that the underlying Trust Manager is doing its job. Even if I set the date and time on my device to be within the validity time of the certificate, the checkServerTrusted line above generates an exception. Why? Clearly I'm doing something wrong. Here is the code for my custom Trust Manager and how I am accessing the Url:

class SSLTrustManager
{
  private X509TrustManager origTrustmanager;

  public SSLTrustManager()
  {
    try
    {
      TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      tmf.init((KeyStore) null);
      TrustManager[] trustManagers = tmf.getTrustManagers();
      this.origTrustmanager = (X509TrustManager) trustManagers[0];
    }
    catch (Exception ex)
    {
    }
  }

  public javax.net.ssl.SSLSocketFactory GetSocketFactory()
  {
    try
    {
      TrustManager[] wrappedTrustManagers = new TrustManager[] {
          new X509TrustManager()
          {
            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
              return origTrustmanager.getAcceptedIssuers();
            }

            public void checkClientTrusted(X509Certificate[] certs, String authType)
            {
              try
              {
                origTrustmanager.checkClientTrusted(certs, authType);
              }
              catch (CertificateException e)
              {
              }
            }

            public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException
            {
              try
              {
                origTrustmanager.checkServerTrusted(certs, authType);
              }
              catch(Exception ex)
              {
              }
            }
          }
       };

      SSLContext sslContext = SSLContext.getInstance("SSL");
      sslContext.init(null, wrappedTrustManagers, new java.security.SecureRandom());
      javax.net.ssl.SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
      return sslSocketFactory;
    }
    catch (Exception ex)
    {
      return null;
    }
  }
}    

Code for accessing a url:

SSLTrustManager sslTrustManager = new SSLTrustManager();
HttpsURLConnection.setDefaultSSLSocketFactory(sslTrustManager.GetSocketFactory());

URL siteUrl = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) siteUrl.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);

DataOutputStream out = new DataOutputStream(conn.getOutputStream());
Nowhere answered 6/12, 2012 at 10:51 Comment(2)
Useful to note that tmf.init((KeyStore) null); will cause the TMF to return the default system TrustManager. This is not documented in the API docs. I was incorrectly using tmf.init(KeyStore.getDefaultKeyStore())Gasp
SSL is now deprecated. When calling SSLContext.getInstance() be sure to use TLSv1.2 or similar.Ellipticity
I
4

If you never initialise the origTrustmanager instance variable, it will have its default value of null, which will indeed cause an NPE any time you're trying to use it.

I've just edited my previous answer on this to show an example of TrustManager initialisation. (I haven't tried on Android, but it works fine in plain Java.)

Be careful not to catch too much. Here, you're catching CertificateException and Exception in your trust manager: this is as good as having nothing, since these methods are meant to throw those exceptions. Make sure you only catch CertificateExpiredException if you want to ignore expiration dates.

Note that this is only a trick that relies on the fact that, in practice, the certificate verification is done after the general trust verification (at least in the OpenJDK implementation). As far as I know, there's nothing in the specifications that says that the certificate expiration is verified after. It was done before other verification on elements of trust and you ignored that exception, you could let more certificate through than you want.

Inconspicuous answered 6/12, 2012 at 11:4 Comment(2)
You're right. I wasn't initializing origTrustmanager. Originally I was, but after moving code around, I somehow deleted it. I added it to the code above. Now it works as expected but I do have another problem with this code. Originally I said that my exception does capture an expired certificate. It does, but all I get is a CertificateException with checkServerTrusted. This could be any certificate exception. How do I tell it's expired? And how do I tell it's the certificate for my site and not someone else's?Nowhere
CertificateExpiredException is a subclass of CertificateException. One of the other problems you may have is that if the phone's time is completely out of range, the CA certs might not be valid at that time either. In this case, you may have to do the whole PKI verification completely (in plain Java, that's documented in the Certification Path API Programmer's Guide, I'm not sure about Android), which is a bit more complex. Regarding the name verification, you need to do this by looking into the certificate Subject Alternative Names (failing that, looking at the CN in the Subject DN).Inconspicuous
B
-1

Use this code This is working for me

TrustManager tm = new X509TrustManager() {
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
};

// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {
    new X509TrustManager() {
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }
        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
    }
};
SSLContext sslContext = null;

try {
    sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, new TrustManager[] { tm }, null);
} catch (Exception e1) {
    e1.printStackTrace();
    return;
}

AsyncSSLSocketMiddleware sslMiddleWare = Ion.getDefault(context).getHttpClient().getSSLSocketMiddleware();
sslMiddleWare.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
sslMiddleWare.setSSLContext(sslContext);

Ion.getDefault(context).getHttpClient().getSSLSocketMiddleware().setTrustManagers(trustAllCerts);
Ion.getDefault(context).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext);

Ion.with(context).load("POST", serverUrl)
    .setHeader("Content-Type", "application/json")
    .setHeader("Accept", "application/json")
    .setLogging("ION_LOGGING", Log.VERBOSE).setJsonObjectBody(json)
Bouchier answered 8/1, 2015 at 14:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.