【问题标题】:Android 2.3.x javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not foundAndroid 2.3.x javax.net.ssl.SSLHandshakeException:java.security.cert.CertPathValidatorException:找不到证书路径的信任锚
【发布时间】:2014-01-23 11:05:43
【问题描述】:

我仅在(可能是某些)2.3.x 设备上收到此错误。它适用于运行高于该版本的 Android 版本的任何其他设备。

这是我的 HTTPRequestController:

public class HttpRequestController {

private final static String TAG = "HttpRequestController";

private static HttpRequestController instance;

public enum Method {
    PUT, POST, DELETE, GET
}

private HttpRequestController() {

}

public static HttpRequestController getInstance() {
    if (instance == null)
        instance = new HttpRequestController();

    return instance;
}

public String doRequest(String url, HashMap<Object, Object> data,
        Method method, String token) throws Exception {

    InputStream certificateInputStream = null;
    if (MyApplication.PRODUCTION) {
        certificateInputStream = MyApplication.context
                .getResources().openRawResource(R.raw.production_cert);
        LogUtils.log("using production SSL certificate");
    } else {
        certificateInputStream = MyApplication.context
                .getResources().openRawResource(R.raw.staging_cert);
        LogUtils.log("using staging SSL certificate");
    }

    KeyStore trustStore = KeyStore.getInstance("BKS");
    try{
    trustStore.load(certificateInputStream,
            "re3d6Exe5HBsdskad8efj8CxZwv".toCharArray());
    } finally {
        certificateInputStream.close();
    }


    TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
    tmf.init(trustStore);
    LogUtils.log("SSL: did init TrustManagerFactory with trust keyStore");
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, tmf.getTrustManagers(), null);
    LogUtils.log("SSL: did init context with trust keyStore");  


    URL request = new URL(url);
    HttpsURLConnection urlConnection = (HttpsURLConnection) request
            .openConnection();

    LogUtils.log("SSL: did open HttpsURLConnection");   

    urlConnection.setHostnameVerifier(new StrictHostnameVerifier());
    urlConnection.setSSLSocketFactory(context.getSocketFactory());
    urlConnection.setConnectTimeout(15000);
    LogUtils.log("SSL: did set Factory and Timeout.");

    if (method != Method.GET){
        urlConnection.setDoOutput(true);
    }
        urlConnection.setDoInput(true);
        urlConnection.setRequestProperty("Content-Type", "application/json");
        urlConnection.setRequestProperty("Accept", "application/json");

    LogUtils.log("SSL: urlConnection did set request properties.");

    if (token != null) {
        urlConnection.setRequestProperty("Authorization", "Token " + token);
    }
        urlConnection.setRequestMethod(method.toString());
        urlConnection.connect();

        LogUtils.log("SSL: urlConnection did connect.");

    if (method != Method.GET) {
        ObjectMapper mapper = new ObjectMapper();
        String jsonValue = mapper.writeValueAsString(data);
        OutputStream os = urlConnection.getOutputStream();
        os.write(jsonValue.getBytes());
        os.flush();
        LogUtils.log(TAG, "Params: " + jsonValue);
    }

    LogUtils.log(TAG, method.toString() + ": " + url);

    InputStream in = null;
    if (urlConnection.getResponseCode() == 200) {
        in = urlConnection.getInputStream();
    } else {
        in = urlConnection.getErrorStream();
    }
    String response = convertStreamToString(in);

    LogUtils.log(TAG, "Got response : " + url);
    LogUtils.log(TAG, "Response : " + response);

    return response;
}

public String convertStreamToString(InputStream inputStream) {
    BufferedReader buffReader = new BufferedReader(new InputStreamReader(
            inputStream));
    StringBuilder stringBuilder = new StringBuilder();

    String line = null;
    try {
        while ((line = buffReader.readLine()) != null) {
            stringBuilder.append(line + "\n");
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return stringBuilder.toString();
}

public HttpClient retrieveHttpClient() {
    return new MyHttpClient(MyApplication.context);
}

}

当我运行命令时:

openssl s_client -debug -connect www.mysitedomain.com:443

我得到了回应:

--
some key stuff 
--
Certificate chain
 0 s:/OU=Domain Control Validated/CN=www.mydomainname.com
   i:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Domain Validation CA - G2
 1 s:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Domain Validation CA - G2
   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
 2 s:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
 some more certificate stuff
-----END CERTIFICATE-----

ubject=/OU=Domain Control Validated/CN=www.mydomainname.com
issuer=/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Domain Validation CA - G2
---
No client certificate CA names sent
---
SSL handshake has read 4091 bytes and written 328 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : DHE-RSA-AES256-SHA
    Session-ID: 57C379C59483809A7FE1BF8E235C5BFA7789E62AAEBCA9BC14B5273F5D1304E7
    Session-ID-ctx: 
    Master-Key: 6FCD498D1294415A42B57420F0C05AB903EF8E56CB6F1530390F73AF5E4CBC22B359D5CDA09811E075A5C598002C380D
    Key-Arg   : None
    Start Time: 1390473282
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---

所以它返回正常...但是对于我测试的 2.3.x 设备,它仍然给我这个错误。

在这之后我得到一个异常:

LogUtils.log("SSL: urlConnection did set request properties.");

这是一个例外:

01-23 10:20:28.459: W/System.err(1623): javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
01-23 10:20:28.459: W/System.err(1623):     at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:477)
01-23 10:20:28.459: W/System.err(1623):     at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:328)
01-23 10:20:28.459: W/System.err(1623):     at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.setupSecureSocket(HttpConnection.java:185)
01-23 10:20:28.459: W/System.err(1623):     at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:433)
01-23 10:20:28.459: W/System.err(1623):     at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.makeConnection(HttpsURLConnectionImpl.java:378)
01-23 10:20:28.459: W/System.err(1623):     at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:205)
01-23 10:20:28.459: W/System.err(1623):     at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:152)

我在这里称呼它的方式是:

String response = HttpRequestController
                            .getInstance()
                            .doRequest(ApiUrls.LOGIN, params, Method.POST, null);

它适用于运行高于 2.3.x 的 Android 版本的任何其他设备(根据我的测试)。

Android 文档似乎没有任何关于 2.3 兼容性的内容。

【问题讨论】:

    标签: android ssl


    【解决方案1】:

    您必须告诉 Android 系统信任您的证书。你的问题是2.3之后的Android接受你的证书是因为它包含在受信任的证书列表中,但在以前的版本中不包括在内,所以有问题。

    我建议你在Android documentation上点赞:

    // 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 URLConnection to use a SocketFactory from our SSLContext
    URL url = new URL("https://certs.cac.washington.edu/CAtest/");
    HttpsURLConnection urlConnection =
        (HttpsURLConnection)url.openConnection();
    urlConnection.setSSLSocketFactory(context.getSocketFactory());
    InputStream in = urlConnection.getInputStream();
    copyInputStreamToOutputStream(in, System.out);
    

    我也在做同样的事情,它在每台设备上都能正常工作,Android 2.3 及更低版本,而且我网站的证书是私有的。

    试一试,告诉我它现在是否有效。

    希望对你有帮助!

    【讨论】:

    • 我使用了与 google doc 相同的代码,但上面写着 /load-der.crt: open failed: ENOENT (No such file or directory)
    • Naz141,你需要在你的项目中包含你想要接受的证书(在这种情况下,文件“load-der.crt”)。您可以加载它,例如从 assets 文件夹中加载。
    【解决方案2】:

    如果有人需要答案,我在google 2天后终于找到了答案。基本上我们需要使用自定义的 TrustManager 来信任 KeyStore 中的 CA。归功于 CustomTrustManager 的 https://github.com/delgurth

    请参考:https://github.com/ikust/hello-pinnedcerts/issues/2

    KeyPinStore.java

    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.KeyManagementException;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.TrustManager;
    public class KeyPinStore {
        private static final String[] certificates = {"certificate1.crt", "certificate2.crt", "certificate3.crt", "certificate4.crt"};
        private static KeyPinStore instance = null;
        private SSLContext sslContext = SSLContext.getInstance("TLS");
    
        public static synchronized KeyPinStore getInstance() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
            if (instance == null) {
                instance = new KeyPinStore();
            }
            return instance;
        }
    
        private KeyPinStore() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            for (int i = 0; i < certificates.length; i++) {
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                InputStream caInput = new BufferedInputStream(Application.context.getAssets().open("certificate/" + certificates[i]));
                Certificate ca;
                try {
                    ca = cf.generateCertificate(caInput);
                    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
                } finally {
                    caInput.close();
                }
    
                // Create a KeyStore containing our trusted CAs
                keyStore.setCertificateEntry("ca" + i, ca);
            }
    
            // Use custom trust manager to trusts the CAs in our KeyStore
            TrustManager[] trustManagers = {new CustomTrustManager(keyStore)};
    
            // Create an SSLContext that uses our TrustManager
            // SSLContext context = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagers, null);
        }
    
        public SSLContext getContext() {
            return sslContext;
        }
    }
    

    CustomTrustManager.java

    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.Principal;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import java.util.Arrays;
    import java.util.List;
    
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    import javax.net.ssl.X509TrustManager;
    
    /**
     * A custom X509TrustManager implementation that trusts a specified server certificate in addition
     * to those that are in the system TrustStore.
     * Also handles an out-of-order certificate chain, as is often produced by Apache's mod_ssl
     */
    public class CustomTrustManager implements X509TrustManager {
    
      private final TrustManager[] originalTrustManagers;
      private final KeyStore trustStore;
    
      /**
       * @param trustStore A KeyStore containing the server certificate that should be trusted
       * @throws NoSuchAlgorithmException
       * @throws KeyStoreException
       */
      public CustomTrustManager(KeyStore trustStore) throws NoSuchAlgorithmException, KeyStoreException {
        this.trustStore = trustStore;
    
        final TrustManagerFactory originalTrustManagerFactory = TrustManagerFactory.getInstance("X509");
        originalTrustManagerFactory.init(trustStore);
    
        originalTrustManagers = originalTrustManagerFactory.getTrustManagers();
      }
    
      /**
       * No-op. Never invoked by client, only used in server-side implementations
       * @return
       */
      public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
      }
    
      /**
       * No-op. Never invoked by client, only used in server-side implementations
       * @return
       */
      public void checkClientTrusted(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {
      }
    
    
      /**
       * Given the partial or complete certificate chain provided by the peer,
       * build a certificate path to a trusted root and return if it can be validated and is trusted
       * for client SSL authentication based on the authentication type. The authentication type is
       * determined by the actual certificate used. For instance, if RSAPublicKey is used, the authType should be "RSA".
       * Checking is case-sensitive.
       * Defers to the default trust manager first, checks the cert supplied in the ctor if that fails.
       * @param chain the server's certificate chain
       * @param authType the authentication type based on the client certificate
       * @throws java.security.cert.CertificateException
       */
      public void checkServerTrusted(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {
        try {
          for (TrustManager originalTrustManager : originalTrustManagers) {
            ((X509TrustManager) originalTrustManager).checkServerTrusted(chain, authType);
          }
        } catch(CertificateException originalException) {
          try {
            // Ordering issue?
            X509Certificate[] reorderedChain = reorderCertificateChain(chain);
            if (! Arrays.equals(chain, reorderedChain)) {
              checkServerTrusted(reorderedChain, authType);
              return;
            }
            for (int i = 0; i < chain.length; i++) {
              if (validateCert(reorderedChain[i])) {
                return;
              }
            }
            throw originalException;
          } catch(Exception ex) {
            ex.printStackTrace();
            throw originalException;
          }
        }
    
      }
    
      /**
       * Checks if we have added the certificate in the trustStore, if that's the case we trust the certificate
       * @param x509Certificate the certificate to check
       * @return true if we know the certificate, false otherwise
       * @throws KeyStoreException on problems accessing the key store
       */
      private boolean validateCert(final X509Certificate x509Certificate) throws KeyStoreException {
        return trustStore.getCertificateAlias(x509Certificate) != null;
      }
    
      /**
       * Puts the certificate chain in the proper order, to deal with out-of-order
       * certificate chains as are sometimes produced by Apache's mod_ssl
       * @param chain the certificate chain, possibly with bad ordering
       * @return the re-ordered certificate chain
       */
      private X509Certificate[] reorderCertificateChain(X509Certificate[] chain) {
    
        X509Certificate[] reorderedChain = new X509Certificate[chain.length];
        List<X509Certificate> certificates = Arrays.asList(chain);
    
        int position = chain.length - 1;
        X509Certificate rootCert = findRootCert(certificates);
        reorderedChain[position] = rootCert;
    
        X509Certificate cert = rootCert;
        while((cert = findSignedCert(cert, certificates)) != null && position > 0) {
          reorderedChain[--position] = cert;
        }
    
        return reorderedChain;
      }
    
      /**
       * A helper method for certificate re-ordering.
       * Finds the root certificate in a possibly out-of-order certificate chain.
       * @param certificates the certificate change, possibly out-of-order
       * @return the root certificate, if any, that was found in the list of certificates
       */
      private X509Certificate findRootCert(List<X509Certificate> certificates) {
        X509Certificate rootCert = null;
    
        for(X509Certificate cert : certificates) {
          X509Certificate signer = findSigner(cert, certificates);
          if(signer == null || signer.equals(cert)) { // no signer present, or self-signed
            rootCert = cert;
            break;
          }
        }
    
        return rootCert;
      }
    
      /**
       * A helper method for certificate re-ordering.
       * Finds the first certificate in the list of certificates that is signed by the sigingCert.
       */
      private X509Certificate findSignedCert(X509Certificate signingCert, List<X509Certificate> certificates) {
        X509Certificate signed = null;
    
        for(X509Certificate cert : certificates) {
          Principal signingCertSubjectDN = signingCert.getSubjectDN();
          Principal certIssuerDN = cert.getIssuerDN();
          if(certIssuerDN.equals(signingCertSubjectDN) && !cert.equals(signingCert)) {
            signed = cert;
            break;
          }
        }
    
        return signed;
      }
    
      /**
       * A helper method for certificate re-ordering.
       * Finds the certificate in the list of certificates that signed the signedCert.
       */
      private X509Certificate findSigner(X509Certificate signedCert, List<X509Certificate> certificates) {
        X509Certificate signer = null;
    
        for(X509Certificate cert : certificates) {
          Principal certSubjectDN = cert.getSubjectDN();
          Principal issuerDN = signedCert.getIssuerDN();
          if(certSubjectDN.equals(issuerDN)) {
            signer = cert;
            break;
          }
        }
    
        return signer;
      }
    }
    

    要使用它,只需获取 SSLSocketFactory 并应用它,例如:

    使用 HttpsURLConnection

    KeyPinStore keystore = KeyPinStore.getInstance();
    SSLSocketFactory sslSF = keystore.getContext().getSocketFactory();
    URL url = new URL("https://certs.cac.washington.edu/CAtest/");
    HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
    urlConnection.setSSLSocketFactory(sslSF);
    InputStream in = urlConnection.getInputStream();
    copyInputStreamToOutputStream(in, System.out);
    

    齐射

    KeyPinStore keystore = KeyPinStore.getInstance();
    SSLSocketFactory sslSF = keystore.getContext().getSocketFactory();
    RequestQueue mRequestQueue = Volley.newRequestQueue(context, new HurlStack(null, sslSF));
    

    【讨论】:

      【解决方案3】:

      注意使用正确的 CA 证书(不要使用站点证书):

      【讨论】:

        猜你喜欢
        • 2014-01-29
        • 2017-04-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-29
        • 2014-09-27
        • 2013-09-27
        • 2017-01-08
        相关资源
        最近更新 更多