This is Xamarin's bug tracking system. For product support, please use the support links listed in your Xamarin Account.
Bug 6501 - Unexpected invalid certificate exception
: Unexpected invalid certificate exception
Status: RESOLVED FIXED
Product: Android
Classification: Xamarin
Component: Class Libraries
: 4.2.x
: All Other
: --- normal
: ---
Assigned To: Bugzilla
:
:
:
:
  Show dependency treegraph
 
Reported: 2012-08-10 10:54 EDT by info
Modified: 2013-02-11 12:45 EST (History)
3 users (show)

See Also:
Tags:
Test Case URL:
External Submit: ---


Attachments

Description info 2012-08-10 10:54:23 EDT
When attempting to connect to the URL "https://kreditkarten-banking.lbb.de/"
via HttpWebRequest an unexpected invalid certificate exception is thrown:


System.Net.WebException: Error getting response stream (Write: The
authentication or decryption has failed.): SendFailure --->
System.IO.IOException: The authentication or decryption has failed. --->
Mono.Security.Protocol.Tls.TlsException: Invalid certificate received from
server. Error code: 0xffffffff800b0109
  at
Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.validateCertificates
(Mono.Security.X509.X509CertificateCollection certificates) [0x00000] in
<filename unknown>:0 
  at
Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.ProcessAsTls1
() [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.Handshake.HandshakeMessage.Process () [0x00000]
in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.ClientRecordProtocol.ProcessHandshakeMessage
(Mono.Security.Protocol.Tls.TlsStream handMsg) [0x00000] in <filename
unknown>:0 
  at Mono.Security.Protocol.Tls.RecordProtocol.InternalReceiveRecordCallback
(IAsyncResult asyncResult) [0x00000] in <filename unknown>:0 
  --- End of inner exception stack trace ---
  at Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback
(IAsyncResult asyncResult) [0x00000] in <filename unknown>:0 
  --- End of inner exception stack trace ---
  at System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult)
[0x00000] in <filename unknown>:0 
  at System.Net.HttpWebRequest.GetResponse () [0x00000] in <filename unknown>:0 
  at Subsembly.Util.HttpClient.GetResponse (System.Net.HttpWebRequest aRequest,
System.String& sContentType) [0x0003e] in
c:\Code\Subsembly_Banking\BotBanking\Util\HttpClient.cs:330 


This exception is unexpected because the very same code works with the very
same URL on MonoTouch (iOS 5.1) and also on Windows Desktop with .NET Framework
2.0 without throwing an exception.
Comment 1 info 2012-08-10 10:56:28 EDT
I forgot: I am using the latest Mono for Android 4.2.5.
Comment 2 Jonathan Pryor 2012-08-10 11:29:11 EDT
Question: Can you connect to your URL from the Android Browser app _without_
getting any dialogs to manually accept the certificate? Which Android version
is this?

I'm unable to reproduce your error on my Galaxy Nexus with Android 4.1/Jelly
Bean.

Mono for Android uses the underlying Android OS for certificate validation. If
the Browser app shows a dialog asking for manual acceptance of the certificate,
then Mono for Android will reject the certificate as invalid (as Android will
be rejecting the certificate). If this is the case, you will need to manually
handle the certificate via the
ServicePointManager.ServerCertificateValidationCallback property:

http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx

Thanks,
 - Jon
Comment 3 info 2012-08-10 11:39:49 EDT
I tested this on the Emulator with API Level 8 and on my LG Optimus 2X with
Android 2.3.4. When entering the URL "https://kreditkarten-banking.lbb.de/" in
the Android Browser on my LG, the web page opens without any prompt.
Comment 4 Jonathan Pryor 2012-08-10 15:55:30 EDT
One apparent oddity: if I open the site on an API 8 emulator within Browser,
hit Menu > More > Page info, the top of the page info dialog says "Error."
Click View Certificate, and Android says it's valid.

Weird. Also, completely unrelated to the problem at hand.
Comment 5 Jonathan Pryor 2012-08-10 16:19:36 EDT
Here's the problem:

https://github.com/mono/mono/blob/master/mcs/class/System/System.Net/ServicePointManager.cs#L451

ServicePointManager.ValidateChain() is invoked as part of SSL validation, and
it invokes monodroidCallback() on Android. If/when that fails, it invokes
ServicePointManager.ServerCertificateValidationCallback.

Here's the interesting bit: the `certs` collection is ~directly visible in the
callback parameters, so we can see what's happening outside the confines of
Android:


  // ServicePointManager.ServerCertificateValidationCallback
  using System;
  using System.Net;
  using System.IO;
  using System.Net.Security;
  using System.Security.Cryptography.X509Certificates;

  class Test {
    public static void Main ()
    {
      ServicePointManager.ServerCertificateValidationCallback = Validator;
      string url = "https://kreditkarten-banking.lbb.de/";
      var request = (HttpWebRequest) WebRequest.Create(url);
      request.Method = "GET";
      var response = (HttpWebResponse) request.GetResponse ();
      int len = 0;
      using (var _r = new StreamReader (response.GetResponseStream ())) {
        char[] buf = new char [4096];
        int n;
        while ((n = _r.Read (buf, 0, buf.Length)) > 0) {
          /* ignore; we just want to make sure we can read */
          len += n;
        }
      }
      Console.WriteLine ("read: {0} bytes", len);
    }

    static bool Validator (object sender, X509Certificate certificate,
        X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
      Console.WriteLine ("Validator!");
      Console.WriteLine ("certificate: {0}", certificate);
      Console.WriteLine ("chain[0]: {0}", chain.ChainElements[0].Certificate);
      string a = certificate.ToString ();
      string b = chain.ChainElements [0].Certificate.ToString ();
      if (a == b)
        Console.WriteLine ("equal!");
      return true;
    }
  }

An interesting thing is apparent: the Validator() `certificate` parameter is
equal to the `chain.ChainElements[0].Certificate` value. This means that in the
internal Mono.Security.X509.X509CertificateCollection collection, the "leaf"
certificate is duplicated, and thus when we ask Android "is this collection of
certificates valid?", the collection we pass similarly has a duplicate entry.

Therein lies the problem: on some versions of Android (API8 in particular; I
haven't tested all emulators), the duplicate certificate cause Android to
report an error, which is why you see the error. (Rephrased: you get an error
because Android doesn't like the certificate chain mono provides.)

API15 and API16, meanwhile, accept the "duplicate" entry, which is why it works
for me on my Galaxy Nexus.
Comment 6 Jonathan Pryor 2012-08-10 16:24:22 EDT
I foresee three solutions:

1. Don't ask Android to validate the entire chain, have Android validate just
the leaf entry (corresponding to the
ServicePointManager.ServerCertificateValidationCallback `certificate`
parameter).

This works, in this case. The problem is that I don't now if it'll work in the
general case, as it's entirely possible for HTTPS servers to provide all
intermediate certificates. If we only validate the leaf, and one of the
intermediate certs isn't installed on the device, things will fail, even if the
HTTPS server provides the intermediates.

I don't like this solution.

2. Assume that the leaf will always be duplicated in the X509Chain, and just
marshal the chain.

This also works in my limited testing, but I don't see any documentation
anywhere ensuring that this should be the case. (MSDN is rather lacking here.
:-/

3. Check for "duplicate" certificate values, and only pass non-duplicate values
to Java.
Comment 7 Jonathan Pryor 2012-08-10 16:39:27 EDT
WORKAROUND: Here is a method you can hookup to
ServicePointManager.ServerCertificateValidationCallback which implements
solution (3), allowing things to work on an API8 emulator:

  static bool Validator (object sender,
      System.Security.Cryptography.X509Certificates.X509Certificate
certificate,
      System.Security.Cryptography.X509Certificates.X509Chain chain,
      System.Net.Security.SslPolicyErrors sslPolicyErrors)
  {
    var sslTrustManager = (IX509TrustManager) typeof (AndroidEnvironment)
      .GetField ("sslTrustManager",
          System.Reflection.BindingFlags.NonPublic |
          System.Reflection.BindingFlags.Static)
      .GetValue (null);
    Func<Java.Security.Cert.CertificateFactory,
      System.Security.Cryptography.X509Certificates.X509Certificate,
      Java.Security.Cert.X509Certificate> c = (f, v) =>
        f.GenerateCertificate (
            new System.IO.MemoryStream (v.GetRawCertData ()))
        .JavaCast<Java.Security.Cert.X509Certificate>();
    var cFactory = Java.Security.Cert.CertificateFactory.GetInstance (
        Javax.Net.Ssl.TrustManagerFactory.DefaultAlgorithm);
    var certs = new List<Java.Security.Cert.X509Certificate>(
        chain.ChainElements.Count + 1);
    certs.Add (c (cFactory, certificate));
    foreach (var ce in chain.ChainElements) {
      if (certificate.Equals (ce.Certificate))
        continue;
      certificate = ce.Certificate;
      certs.Add (c (cFactory, certificate));
    }
    try {
      sslTrustManager.CheckServerTrusted (certs.ToArray (),
          Javax.Net.Ssl.TrustManagerFactory.DefaultAlgorithm);
      return true;
    }
    catch (Exception e) {
      return false;
    }
  }
Comment 8 Jonathan Pryor 2012-08-13 14:35:56 EDT
Fixed in d4b59e0d.
Comment 9 Jonathan Pryor 2012-08-13 14:40:20 EDT
Correction: Comment #5 implied that the error was due to duplicate
certificates. That may be the problem, but there is another problem as well:
`certs` contains an _unordered_ collection of certificates. The ordering is
"fixed" into an ordered into a "certificate chain" when the X509Chain is built.
The result is that, for https://kreditkarten-banking.lbb.de/, `certs` and
`chain` have a different ordering of certificates, and  Android API8 requires
the correct ordering in order for things to work as well.

SSL is hard; let's flee in terror!
Comment 10 kenny 2013-02-08 17:44:34 EST
Jonathan,

tl;dr use the explicit type of CertificateFactory in your "getInstance" request
which should be "X.509"

That code snippet above may throw an exception. You're getting the default type
of TrustManagerFactory and using it to request an instance of a
CertificateFactory. I think it was an error that the default
TrustManagerFactory used to return "X.509"  If you look at the Standard Names
documentation, you'll see the only supported algorithm is listed as "PKIX" for
TrustManager:
http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html#TrustManagerFactory
Comment 11 Jonathan Pryor 2013-02-11 12:45:18 EST
@kenny: Thank you for pointing that out. The 4.8+ release will explicitly use
the X.509 provider instead of the TrustManagerFactory.DefaultAlgorithm
provider.

Note You need to log in before you can comment on or make changes to this bug.