在Android中覆盖SSL Trust Manager

6
我正在尝试覆盖Android中的信任管理器。我希望让底层的信任管理器检查证书,但我需要确定证书是否过期。如果证书已过期,则我需要忽略它并接受该证书。某些移动设备在电池被拆卸后会重置日期,导致证书看起来已过期。即使发生这种情况,我的应用程序也必须继续运行。
我遇到的问题是,这行代码会抛出NullPointerException:
origTrustmanager.checkServerTrusted(certs, authType);

根据文档,checkServerTrusted不应该抛出NullPointerExeption。certs中有两个项。authType设置为"RSA"。如果我不实现自定义Trust Manager,将会抛出一个明确表明证书已过期的异常,因此我知道基础Trust Manager正在发挥作用。即使我将设备的日期和时间设置为证书的有效时间内,上面的checkServerTrusted行仍会生成一个异常。为什么?显然我做错了什么。这是我的自定义Trust Manager的代码以及我如何访问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;
    }
  }
}    

访问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());

4
需要注意的是,tmf.init((KeyStore) null); 会导致 TMF 返回默认的系统 TrustManager。这一点在 API 文档中没有记录。我之前错误地使用了 tmf.init(KeyStore.getDefaultKeyStore()) - Dori
SSL现在已经被弃用。当调用SSLContext.getInstance()时,请确保使用TLSv1.2或类似的协议。 - Alex W
2个回答

4
如果您从未初始化origTrustmanager实例变量,它将具有默认值null,这确实会在您尝试使用它时导致NPE。

我刚刚编辑了我的以前的答案,展示了TrustManager初始化的示例。(我没有在Android上尝试过,但在普通Java中可以正常工作。)

要小心不要捕获太多内容。在这里,您正在捕获您的信任管理器中的CertificateExceptionException:这就像什么都没有一样,因为这些方法应该抛出这些异常。如果您想忽略到期日期,请确保只捕获CertificateExpiredException

请注意,这只是一个技巧,依赖于事实,在实践中,证书验证是在通用信任验证之后进行的(至少在OpenJDK实现中是如此)。据我所知,规范中没有任何说明证书到期是在之后验证的。如果在信任元素的其他验证之前执行了此操作并忽略了该异常,您可能会让更多的证书通过。


你说得对。我没有初始化origTrustmanager。最初我是这样做的,但在移动代码后,不知怎么地把它删除了。我已将其添加到上面的代码中。现在它按预期工作,但我在这段代码中还有另一个问题。最初我说我的异常确实捕获了一个过期的证书。它确实捕获了,但我得到的只是一个带有checkServerTrusted的CertificateException。这可能是任何证书异常。如何告诉它已经过期?如何告诉它是我的站点的证书而不是别人的? - Johann
CertificateExpiredExceptionCertificateException的子类。你可能会遇到的另一个问题是,如果手机的时间完全超出范围,那么CA证书在那个时间也可能无效。在这种情况下,您可能需要完全执行整个PKI验证(在普通Java中,这在Certification Path API程序员指南中有记录,我不确定Android是否有),这会更加复杂。关于名称验证,您需要通过查看证书主题备用名称(如果失败,则查看主题DN中的CN)来进行此操作。 - Bruno

-1

使用这段代码 这对我有效

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接