Bug 18552 - Root CA element is missing from the chain
Summary: Root CA element is missing from the chain
Status: RESOLVED INVALID
Alias: None
Product: Android
Classification: Xamarin
Component: BCL Class Libraries ()
Version: 4.10.0.x
Hardware: PC Windows
: Normal normal
Target Milestone: ---
Assignee: Marek Habersack
URL:
Depends on:
Blocks:
 
Reported: 2014-03-24 10:10 UTC by patrickg
Modified: 2015-02-27 20:20 UTC (History)
5 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED INVALID

Description patrickg 2014-03-24 10:10:05 UTC
When trying to validate the root ca certificate in a certificate chain using the ServerCertificateValidationCallback, the root ca certificate is missing.

Consider the following chain of certificates that is received from https://login.windows.net:
Baltimore CyberTrust Root
 |_ Microsoft Internet Authority
    |_ MSIT Machine Auth CA 2
       |_ graph.windows.net

Invoking the code (causing the callback method to execute) from within a unit test on my windows machine I receive 4 chain elements in the chain property of the callback method.

When I run that same code on the device the 4th element (Baltimore CyberTrust Root) is missing.

Therefore I cannot validate the complete chain upon the root.

Also I tried this with the version 4.12.0 and it didn't work with that either.
Comment 2 Marek Habersack 2014-06-13 12:56:06 UTC
The issue here is that the website does not send the root certificate with the TLS handshake (common practice) leaving it for the
client to obtain root cert to validate the chain. Unfortunately, it is hard to replicate the .NET X509Chain functionality using OS
native APIs (on desktop there exists a Mono utility called mozroots which downloads and installs the root certificates in local
store so that Mono can read them and include in its chain but in the mobile operating systems this kind of action cannot be performed
and no local certificate store exists).

However, the validation IS performed for you by Android-specific code in Xamarin.Android and its results are available for you in the
callback. Consider the callback signature:

bool ValidateChain (
   object sender, 
   System.Security.Cryptography.X509Certificates.X509Certificate certificate, 
   System.Security.Cryptography.X509Certificates.X509Chain chain, 
   System.Net.Security.SslPolicyErrors sslPolicyErrors);

Note the last parameter, sslPolicyErrors whose type is documented in http://iosapi.xamarin.com/?link=T:System.Net.Security.SslPolicyErrors
If there have been any problems with the certificate chain, the RemoteCertificateChainErrors flag will be set in that parameter.
This validation is performed by Xamarin.Android just before invoking the callback. If you need finer control over the validation you can
implement the code yourself by using the plaform APIs. Sample code follows:

bool ValidateChainNative (System.Security.Cryptography.X509Certificates.X509Chain chain)
{
  var tmf = Javax.Net.Ssl.TrustManagerFactory.GetInstance (Javax.Net.Ssl.TrustManagerFactory.DefaultAlgorithm);
  tmf.Init ((KeyStore)null);
  Javax.Net.Ssl.ITrustManager genericTM = tmf.GetTrustManagers ().FirstOrDefault (tm => {
    try {
      if (tm.JavaCast<Javax.Net.Ssl.IX509TrustManager>() != null)
        return true;
    } catch {
    }
    return false;
  });
  
  Javax.Net.Ssl.IX509TrustManager trustManager = null;
  if (genericTM != null)
    trustManager = genericTM.JavaCast<Javax.Net.Ssl.IX509TrustManager>();
			
  if (trustManager == null)
    return false;
			
  var javaCerts = new List <Java.Security.Cert.X509Certificate> ();
  var certFactory = Java.Security.Cert.CertificateFactory.GetInstance ("X.509");
  foreach (var chainItem in chain.ChainElements)
    javaCerts.Add (certFactory.GenerateCertificate (new MemoryStream (chainItem.Certificate.GetRawCertData ())).JavaCast<Java.Security.Cert.X509Certificate>());

  Java.Security.Cert.X509Certificate[] certs = javaCerts.ToArray ();
  try {
    trustManager.CheckServerTrusted (certs, Javax.Net.Ssl.TrustManagerFactory.DefaultAlgorithm);
    return true;
  } catch {
    // ignore
  }

  // Note - it can throw an exception if it isn't able to validate the entire chain for some
  // reason. You can validate just the first cert in the array then.
  try {
    trustManager.CheckServerTrusted (new [] {certs [0]}, Javax.Net.Ssl.TrustManagerFactory.DefaultAlgorithm);
    return true;
  } catch {
    return false;
  }

  return true;
}

// This is the ServicePointManager.ServerCertificateValidationCallback handler
bool ValidateChain(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
  chain.Build(new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate));
  return ValidateChainNative (chain);
}

Hope that helps!