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

Posted

技术标签:

【中文标题】Android java.security.cert.CertPathValidatorException:找不到证书路径的信任锚【英文标题】:Android java.security.cert.CertPathValidatorException: Trust anchor for certification path not found 【发布时间】:2017-01-08 21:31:33 【问题描述】:

android 应用程序由三个主机进行身份验证和授权。最终主机是 REST API。第一次使用 Oauth 身份验证和授权过程,它可以正常工作。

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

谁能解释这个异常背后的场景以及应用程序出了什么问题。如果根据this SO answer 忽略认证异常,则此方法有效。

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;

我使用 Okhttp 3 处理 http 请求。任何建议都将有助于解决问题。如果我使用上面的代码 sn-p,请告诉我,这是否违反安全规定?会影响应用的安全吗?

【问题讨论】:

注意:当您捕获并忽略 CertificateException 时,您的 checkServerTrusted 实现毫无用处。因此,所有服务器证书(受信任和不受信任)都被接受! @Robert 感谢您的澄清。是的从安全角度来看这是不安全且无用的,我已经找到了答案中描述的正确解决方案。 【参考方案1】:

我正在回答这个问题,以便根据 android 开发者网站提供有关场景和解决方案的想法,以使其他人受益。我已经使用自定义信任管理器解决了这个问题。

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

android developer site 中有一个解决方案。它建议使用信任此服务器证书的自定义信任管理器,或者建议服务器在服务器链中包含中间 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();

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

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

InputStream caInput = new BufferedInputStream(context
                .getResources().openRawResource(R.raw.certfilename));

其中 certfilename 是放置在 resources/raw 文件夹中的证书文件。此外,okhttp 的 sslSocketFactory(SSLSocketFactory sslSocketFactory) 已被弃用,可以使用 okhttp api 文档中的建议方法。

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

openssl s_client -connect server-address:port -showcerts

因为我曾经从 firefox 中获取它并面临被病毒防护更改的情况。

【讨论】:

【参考方案2】:

    将您的cert.pem 粘贴到原始文件夹中

    创建一个方法

    private SSLSocketFactory getSSLSocketFactory()
        try 
            CertificateFactory cf;
            cf = CertificateFactory.getInstance("X.509");
    
            Certificate ca;
            InputStream cert = context.getResources().openRawResource(R.raw.cert);
            ca = cf.generateCertificate(cert);
            cert.close();
    
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore   = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);
    
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);
    
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);
    
            return sslContext.getSocketFactory();
    
        
        catch (Exception e)
            return null;
        
    
    

    这样打电话

    final OkHttpClient client = new OkHttpClient();
     //pass getSSLSocketFactory() in params
     client.setSslSocketFactory(getSSLSocketFactory());
    
     String appURl = context.getString(R.string.apis_app_url);
    
     final RestAdapter restAdapter = new RestAdapter.Builder()
             .setEndpoint(appURl).setClient(new OkClient(client)).
                     build();
    

【讨论】:

setSslSocketFactory 已弃用。

以上是关于Android java.security.cert.CertPathValidatorException:找不到证书路径的信任锚的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )

android 21 是啥版本

Android逆向-Android基础逆向(2-2)

【Android笔记】android Toast

图解Android - Android核心机制

Android游戏开发大全的目录