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

75,887

Solution 1

It looks like the issuer of your certificate is not in the trust store of the 2.3 devices.

Take a look at the root and intermediate ca's of your GoDaddy certificate and check whether the certificates are present on your 2.3 device.

See http://www.andreabaccega.com/blog/2010/09/23/android-root-certification-authorities-list/ for obtaining a list of 2.3 certificates.

When only the root CA is available make sure that your webserver also serves the intermediate certificates upon request.

Solution 2

I went through LOTS of places in SO and the web to solve this thing. This is the code that worked for me (Android 21):

ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509","BC");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = "alias";//cert.getSubjectX500Principal().getName();

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null);
trustStore.setCertificateEntry(alias, cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(trustStore, null);
KeyManager[] keyManagers = kmf.getKeyManagers();

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
URL url = new URL(someURL);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());

app.certificateString is a String that contains the Certificate, for example:

static public String certificateString=
    "-----BEGIN CERTIFICATE-----\n" +
    "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
    "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
    ... a bunch of characters...
    "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
    "-----END CERTIFICATE-----";

I have tested that you can put any characters in the certificate string, if it is self signed, as long as you keep the exact structure above. I obtained the certificate string with my laptop's Terminal command line. I you need to know more details, let me know.

Solution 3

If above all answers not work and you get any kind of issue in publish app when using android 30 as compile and target sdk version. Please download your ssl .cert file from server. and just put it in raw folder. create network_security_config in XML folder. use line in menifest inside application tag

android:networkSecurityConfig="@xml/network_security_config"

and use below mention code in network_security_config file.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
    <domain includeSubdomains="true">your server domain</domain>
    <trust-anchors>
        <certificates src="@raw/your server cerificate file name"/>
    </trust-anchors>
</domain-config>
</network-security-config>

Solution 4

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, this is because in Android 2.3.x, the keystore is not being properly used. Credit to https://github.com/delgurth for the CustomTrustManager.

Please refer: 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;
  }
}

To use it, just get the SSLSocketFactory and apply it, eg:

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

with Volley

KeyPinStore keystore = KeyPinStore.getInstance();
SSLSocketFactory sslSF = keystore.getContext().getSocketFactory();
RequestQueue mRequestQueue = Volley.newRequestQueue(context, new HurlStack(null, sslSF));
Share:
75,887
joseph
Author by

joseph

Does anyone read these?

Updated on July 09, 2022

Comments

  • joseph
    joseph almost 2 years

    In my server (production server), I have a goDaddy ssl certificate. I have both iOS and Android apps connecting with the server, iOS connecting with no problems, android with versions 4.* everything is good, but with devices with 2.3.* I always get a SSLHandshakeException.

    I did exactly like on Android Developers page (https://developer.android.com/training/articles/security-ssl.html).

    I already saw similar threads here in Stack Overflow (here) but none is helping.

    Then I saw this thread talking about Extended Key Usage, but when debugging I get the following information:

    [2]: OID: 2.5.29.37, Critical: false
    Extended Key Usage: [ "1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2" ]
    

    So I guess the certificate is not "forcing" Extended Key Usage.

    Also on this thread there are some other possible causes such as date/time being completely wrong, which are all not existent.

    Taking that into consideration, I now don't know where the problem might be.

    Any suggestions?

    EDIT: StackTrace below:

    08-04 16:54:30.139: W/System.err(4832): Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    08-04 16:54:30.149: W/System.err(4832):     at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:161)
    08-04 16:54:30.149: W/System.err(4832):     at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:664)
    08-04 16:54:30.149: W/System.err(4832):     at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
    08-04 16:54:30.159: W/System.err(4832):     at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:474)
    
  • joseph
    joseph over 9 years
    My webserver also serves the intermediate certificate, but it is not present on that list. According to the documentation, by creating a keystore with my certificate and adding a trustManager (which I am doing) would work, but currently it is obviously not working. Any suggestions?
  • userM1433372
    userM1433372 over 9 years
    What is the url of your webserver? Can I access it? Are you referring to a trustmanager on the server or in your Android App?
  • joseph
    joseph over 9 years
    It looks like the problem was that when running the code from Android Development Documentation (in "Unknown certificate authority") was causing the error. I removed it and it is now working on all devices. I marked this answer as the correct one because in fact the code was changing my device CA. @jww as I said, my code was exactly as on Android Documentation, and provided the link. I am free to not give you the link, providing incorrect information is very different from not providing some information. The comunity was still able to help me, as userM1433372 did.
  • ivanleoncz
    ivanleoncz over 7 years
    Hi! Would you like to help to solve this? stackoverflow.com/questions/39553999/… I see tons of questions around self-signed SSL certificates, but most of the questions, are outdated (libraries deprecated by Android,, for example), and I'm using Android Volley. I also see tons of questions with answers like "nuke all SSL certificates", which really makes me feel upset (at least). I would like to solve this problem, and also, to cast a light over it, stopping this "crap" about nuking ssl certificates.
  • Parth Anjaria
    Parth Anjaria over 7 years
    Great Answer, Worked very well. Thank you @Josh
  • pa1.Shetty
    pa1.Shetty over 4 years
    where can i get certificate1.crt, certificate2.crt , certificate3.crt ?
  • Tertium
    Tertium over 3 years
    Thanks man, the only method works in android 29 at least.
  • Kishan Solanki
    Kishan Solanki almost 2 years
    If my server domain is amazon then what should be written into "your server domain"? www.amazon.com or amazon.com or amazon.in or secure.amazon.com? what?