Android java.security.cert.CertPathValidatorException: Trust anchor for certification path not found

时间:2016-09-01 06:24:02

标签: java android security ssl okhttp3

There are three hosts that an android app do the authentication and authorization. Final host is the REST API. For the first time using Oauth authentication and authorization process it works without issue.

But if user kills the app after login and accessing the services provided by REST API and then again open the app, this issue arise. In this time authentication and authorization process is not happening, only the REST API. It caused to java.security.cert.CertPathValidatorException but it was working during the first use (login and then use the app).

Can someone explains the scenario behind this exception and whats wrong with the app. This works if certification exceptions are ignored as bellow according to 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;

I am using Okhttp 3 for the http requests. Any suggestion would help to solve the issue. And please let me know if I use above code snippet, is it a security violation? will it effect to the security of the app?

2 个答案:

答案 0 :(得分:19)

我正在回答这个问题,根据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();

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

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

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

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

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

openssl s_client -connect {server-address}:{port} -showcerts

因为我曾经从firefox那里抓过它,并遇到被病毒防护改变的情况。

答案 1 :(得分:1)

  1. cert.pem粘贴到原始文件夹

  2. 创建方法

    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;
        }
    }
    
  3. 像这样打电话

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