Android java.security.cert.CertPathValidatorException:未找到证书路径的信任锚

Ruw*_*han 11 java security ssl android okhttp3

Android应用程序有三个主机进行身份验证和授权.最终主机是REST API.第一次使用Oauth身份验证和授权过程,它没有问题.

但是,如果用户在登录后杀死应用程序并访问REST API提供的服务,然后再次打开应用程序,则会出现此问题.在这种情况下,身份验证和授权过程不会发生,只有REST API.它导致java.security.cert.CertPathValidatorException 但它在第一次使用时工作(登录然后使用应用程序).

有人可以解释这个异常背后的情况,并且该应用程序有什么问题.如果根据此SO答案忽略认证例外,则此方法有效.

SSLSocketFactory sslSocketFactory = null;

        try {
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            // Initialise the TMF as you normally would, for example:
            try {
                tmf.init((KeyStore)null);
            } catch(KeyStoreException e) {
                e.printStackTrace();
            }
            TrustManager[] trustManagers = tmf.getTrustManagers();

            final X509TrustManager origTrustmanager = (X509TrustManager)trustManagers[0];

            // Create a trust manager that does not validate certificate chains
            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) {
                                e.printStackTrace();
                            }
                        }

                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                            try {
                                origTrustmanager.checkServerTrusted(certs, authType);
                            } catch(CertificateException e) {
                                e.printStackTrace();
                            }
                        }
                    }
            };
            //TrustManager[] trustAllCerts = TrustManagerFactory.getInstance("SSL").getTrustManagers();

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, wrappedTrustManagers, new java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            sslSocketFactory = sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
        return sslSocketFactory;
Run Code Online (Sandbox Code Playgroud)

我正在使用Okhttp 3来获取http请求.任何建议都有助于解决问题.如果我使用上面的代码片段,请告诉我,是否违反安全规定?它会对应用程序的安全性产生影响吗?

Ruw*_*han 22

我正在回答这个问题,根据Android开发者网站提供有关场景和解决方案的建议.我使用自定义信任管理器解决了这个问题

问题出在服务器证书上,它错过了中间证书颁发机构.但是,第一个流证书路径以某种方式完成,结果是证书路径验证成功.

android开发者网站有一个解决方案.它建议使用信任此服务器证书的自定义信任管理器,或者建议服务器将中间CA包含在服务器链中.

定制信托经理.来源:https://developer.android.com/training/articles/security-ssl.html#UnknownCa

// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
Certificate ca;
try {
    ca = cf.generateCertificate(caInput);
    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
    caInput.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 context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Tell the okhttp to use a SocketFactory from our SSLContext
OkHttpClient okHttpClient client = new OkHttpClient.Builder().sslSocketFactory(context.getSocketFactory()).build();
Run Code Online (Sandbox Code Playgroud)

更新:我的问题是在从服务器端添加到证书链中的中间证书颁发机构后解决的.这是最好的解决方案,将证书与应用程序捆绑在一起需要在证书过期时更新应用程序或与证书管理相关的任何其他问题.

更新:03/09/2017最简单的加载证书文件的方法我发现是使用原始资源.

InputStream caInput = new BufferedInputStream(context
                .getResources().openRawResource(R.raw.certfilename));
Run Code Online (Sandbox Code Playgroud)

其中certfilename是放在resources/raw文件夹中的证书文件.okhttp sslSocketFactory(SSLSocketFactory sslSocketFactory)也被弃用了,可以使用okhttp api doc中的建议方法.

此外,从服务器获取证书时,最好使用openssl.

openssl s_client -connect {server-address}:{port} -showcerts
Run Code Online (Sandbox Code Playgroud)

因为我曾经从firefox那里抓住它,并面临被病毒防护改变的情况.