3

Thing I am trying to do is quite simple - I want to obtain full certificate chain when connecting to a server from iOS and Android app.

In iOS, I am using NSURLSession and overriding URLSession:didReceiveChallenge: method in which I am able to obtain certificate chain which in this case looks like expected:

[leaf certificate] - [intermediate certificate] - [root certificate]

Lovely.

Now I am trying to do the same thing on Android device with usage of HttpsURLConnection. After connecting to the server, I am obtaining the certificate chain (or at least I am hoping that this is the method for it) with usage of getServerCertificates() method. This returns me Certificate[] object in which I am getting the chain that looks like this:

[leaf certificate] - [intermediate certificate]

So, no root certificate on Android device.

Do you have any idea how to obtain root certificate from the chain in Android?

Thank you in advance.

uerceg
  • 4,637
  • 6
  • 45
  • 63

1 Answers1

5

Your question is only simple apparently. I was reviewing the section of server certificates in TLS specification. See this post

The server must send an orderer certification chain starting with server certificate and intermediates but the CA root is optional, because is required by the standard that root certificate be distributed independently. It is common for servers not to include CA root

The client validates the chain looking for the root CA in the truststore. The validation is performed by X509TrustManager It would be desirable to return the root certificate found, but, as you can see, the method in charge of validation finish silently

 public void checkServerTrusted(X509Certificate[] chain, String authType)

EDITED - How to get trusted root from a HTTPS connection

You can verify the intermediate certificate with the list of available trusted certitificates to check which of them is the issuer. (Extracted code from from here and here

TrustManagerFactory tmf = TrustManagerFactory.getInstance(
        TrustManagerFactory.getDefaultAlgorithm());
// Initialise the TMF as you normally would, for example:
tmf.init((KeyStore)null); 

//get default X509TrustManager
TrustManager[] trustManagers = tmf.getTrustManagers();
final X509TrustManager x509Tm = (X509TrustManager)trustManagers[0];

//trusted certificate issuers
X509Certificate issuers[] = x509Tm.getAcceptedIssuers();

//Perform connection...
HttpsURLConnection conn =....

//get the last intermediate certificate from server certificates. 
//Fixme: if the server returns alsothe root certificate...
Certificate cert[] = conn.getServerCertificates();
X509Certificate intermediate = (X509Certificate)cert[cert.length-1];

for (int i = 0; i < issuers.length;i++){
    try{
        intermediate.verify(issuers[i].getPublicKey());
            //Verification ok. issuers[i] is the issuer
            return issuers[i];
        } catch (Exception e){}
    }
}
Community
  • 1
  • 1
pedrofb
  • 37,271
  • 5
  • 94
  • 142
  • Thanks for the answer. Do you have any explanation why am I getting the root certificate in iOS case, but on Android not? I am sending the request to identical endpoint in both cases. – uerceg Feb 13 '17 at 13:27
  • Assuming the server is not providing the root CA in the chain(you can check it in ssllabs.com), iOS is building the full certification chain using the matching certificate of the trust store, whereas Android is validating the same but is not adding the root certificate. In my opinion Android (and Java) should provide this information. – pedrofb Feb 13 '17 at 13:44
  • Yep, thank you for the info, makes sense. I tried to override `X509TrustManager`, but still no luck. Do you maybe know if there's a way in Android to obtain the root certificate from the device which was used to validate the intermediate one from the chain? (pretty much to obtain this root one which iOS is returning in it's API, but probably reading it from the device itself) – uerceg Feb 13 '17 at 15:27
  • Once the connection is successful, you can verify the intermediate certificate with the list of available trusted certitificates to check which of them is the issuer. Use `certificate.verify(trustedCertificate.getPublicKey())` You can get the list of trusted issuers with the method `getAcceptedIssuers()` from current `X509TrustManager` – pedrofb Feb 13 '17 at 16:15
  • 1
    You are the jedi. Exactly what I needed. Thanks a bunch! – uerceg Feb 13 '17 at 16:58