如何让信任锚在 Android API 级别 16-19 上正常工作

Posted

技术标签:

【中文标题】如何让信任锚在 Android API 级别 16-19 上正常工作【英文标题】:How to get Trust anchors to work properly on Android API levels 16-19 【发布时间】:2019-03-08 21:35:47 【问题描述】:

我正在开发一个必须支持回 API 级别 16 并要求我对动态 URL 进行网络调用的项目。我遇到的问题是服务器最近必须将 TLS 更新为 1.2 以符合 PCI 标准,默认情况下在 android API 16-19 上禁用。虽然启用此功能并不困难,但我遇到了仅针对这些 API 级别的 X509 证书的问题。

我对此进行了大量研究,虽然所有这些链接都很有用:

1) How to enable TLS 1.2 support in an Android application (running on Android 4.1 JB)

2) How to use a self signed certificate to connect to a Mqtt server in Android (paho client)?

3)SSLSocketFactory in java

4) Allowing Java to use an untrusted certificate for SSL/HTTPS connection

5)Trust Anchor not found for Android SSL Connection

6) SSLHandshakeException: Trust anchor for certification path not found. Only on Android API < 19

7) Trusting all certificates using HttpClient over HTTPS

他们没有解决我看到这个异常的核心问题:

 com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
     at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:151)
     at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:112)
 Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:409)
     at com.android.okhttp.Connection.upgradeToTls(Connection.java:146)
     at com.android.okhttp.Connection.connect(Connection.java:107)
     at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294)
     at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
     at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
     at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
     at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296)
     at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:503)
     at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136)
     at com.android.volley.toolbox.HurlStack.performRequest(HurlStack.java:110)
     at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:96)
    ... 1 more

当我尝试连接到需要 TLS1.2 的 URL 时,例如 https://www.ssllabs.com/ssltest/viewMyClient.html

本质上,这归结为 TrustManager 的问题。

这里有两种不同的方式来获取 TrustManager 对象(第一种比第二种更安全,因为第二种信任所有证书,这使得 SSL 成为一个争论点):

第一种方法:

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
        TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

第二种方法:

X509TrustManager trustManager = new X509TrustManager() 
    public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException 
    public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException 
    public X509Certificate[] getAcceptedIssuers() 
        return null;
    
;

然后将它们与以下代码一起使用:

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new TrustManager[]  trustManager , null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

如果我在 API 级别 20+ 上使用第一个 TrustManager,上面列出的 url 将正常工作并且页面将加载。如果我在 API 级别 16-19 上使用第一个,它将抛出上面列出的异常。

如果我在任何 API 级别 16+ 上使用第二个 TrustManager,该调用将起作用,但是,它会在我的应用程序中留下一个安全漏洞,因为我现在信任所有证书并且让自己容易受到各种攻击。

这里的另一个注意事项是,虽然上述示例错误来自 Volley 库,但在使用 Retrofit 时仍然会发生相同的结果。

因此,我的问题是,我如何利用 X509TrustManager 与 API 级别 16-19 上的 This One 等网站合作,就像他们在 API 级别 20+ 上一样?

感谢您的宝贵时间。

【问题讨论】:

我正在使用套接字的项目中遇到同样的问题。在 API 20+ 上一切正常,但在以下任何情况下,我都会遇到与您描述的相同的信任锚问题,即使我自己提供了正确的证书也是如此。你最终能解决这个问题吗? @Sandervan'tVeer 不,很遗憾。我什至有一周的赏金没有成功的解决方案,所以这可能是那些可能无法解决的问题之一。 【参考方案1】:

这个错误可以用这个方法

此错误来自“https://”,我们在基本网址中使用了“s”

并将 connectTimeout 时间更改为您的相应时间

public static OkHttpClient getUnsafeOkHttpClient() 
    try 

        final TrustManager[] trustAllCerts = new TrustManager[]
                new X509TrustManager() 
                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException 
                    

                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException 
                    

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() 
                        return new java.security.cert.X509Certificate[];
                    
                
        ;

        // Install the all-trusting trust manager
        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

        // Create an ssl socket factory with our all-trusting manager
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
        builder.hostnameVerifier(new HostnameVerifier() 
            @Override
            public boolean verify(String hostname, SSLSession session) 
                return true;
            
        );
        builder.connectTimeout(6000, TimeUnit.SECONDS)
                .readTimeout(6000, TimeUnit.SECONDS)
                .writeTimeout(6000, TimeUnit.SECONDS).connectionPool(new ConnectionPool(50, 50000, TimeUnit.SECONDS));
        return builder.build();
     catch (Exception e) 
        throw new RuntimeException(e);
    

【讨论】:

据我所知,更改我的 TrustManager 以匹配您上面的代码只会允许所有事情通过,正如我在问题中提到的,这是我试图避免的一个主要安全漏洞。

以上是关于如何让信任锚在 Android API 级别 16-19 上正常工作的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 API 级别 16 注册 Android 令牌

android api 16 中的 BigDecimal

Android studio:调用需要 API 级别 16 错误

当散列锚在 URL 中时显示选择 DIV

Android API 级别 16 及更高级别的 Torch 应用程序

Android 和 Eclipse:更改应用程序 API 级别