Android 2.3.x javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am receiving this error only on (maybe some) 2.3.x devices. it works for any other devices running an Android version above that.
Here is my 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
It works for any other devices running an Android version above 2.3.x (from what I have tested).
The Android documentation appears to have nothing written on the subject of 2.3 compatibility.
回答1:
You have to tell the Android system to trust your certificate. Your problem is that Android after 2.3 accepts your certificate because it has it included on the trusted certificates list, but on the previous versions is not included, so, there is the problem.
I recommend you doing like on the 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);
I am doing the same, and it is working properly on every devices, with Android 2.3 and below, and the certificate of my site is a private one.
Just try it, and tell me if it is working now.
Hope it helps you!
回答2:
In case someone need the answer, I finally found the answer after 2 days of google. Basically we need to use custom TrustManager to trusts the CAs in our KeyStore. Credit to https://github.com/delgurth for the CustomTrustManager.
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 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 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 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 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; } }
To use it, just get the SSLSocketFactory and apply it, eg: