Bug 10366 - Can't validate server certificate while using SslStream or SslClientStream
Summary: Can't validate server certificate while using SslStream or SslClientStream
Alias: None
Product: iOS
Classification: Xamarin
Component: BCL Class Libraries ()
Version: 6.0.x
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: (C7)
Assignee: Martin Baulig
Depends on:
Reported: 2013-02-18 03:48 UTC by Ilya Skriblovsky
Modified: 2016-01-18 16:24 UTC (History)
8 users (show)

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

Modified patch to allow validation without a RemoteCertificateValidationCallback (1.34 KB, patch)
2014-04-18 01:22 UTC, Brendan Zagaeski (Xamarin Team, assistant)

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:

Description Ilya Skriblovsky 2013-02-18 03:48:20 UTC

System.Net.Security.SslStream and Mono.Security.Protocol.Tls.SslClientStream seems not to check server certificate against iOS root certificates. It constantly throws an exception:

[ERROR] FATAL UNHANDLED EXCEPTION: System.IO.IOException: The authentication or decryption has failed. ---> Mono.Security.Protocol.Tls.TlsException: Invalid certificate received from server.
  at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.validateCertificates (Mono.Security.X509.X509CertificateCollection certificates) [0x0027b] in /Developer/MonoTouch/Source/mono/mcs/class/Mono.Security/Mono.Security.Protocol.Tls.Handshake.Client/TlsServerCertificate.cs:327 
  at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.ProcessAsTls1 () [0x00054] in /Developer/MonoTouch/Source/mono/mcs/class/Mono.Security/Mono.Security.Protocol.Tls.Handshake.Client/TlsServerCertificate.cs:105 
  at Mono.Security.Protocol.Tls.Handshake.HandshakeMessage.Process () [0x00037] in /Developer/MonoTouch/Source/mono/mcs/class/Mono.Security/Mono.Security.Protocol.Tls.Handshake/HandshakeMessage.cs:105 
  at Mono.Security.Protocol.Tls.ClientRecordProtocol.ProcessHandshakeMessage (Mono.Security.Protocol.Tls.TlsStream handMsg) [0x00039] in /Developer/MonoTouch/Source/mono/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/ClientRecordProtocol.cs:81 
  at Mono.Security.Protocol.Tls.RecordProtocol.InternalReceiveRecordCallback (IAsyncResult asyncResult) [0x00127] in /Developer/MonoTouch/Source/mono/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/RecordProtocol.cs:397 

Code is:

var tcpClient = new TcpClient("google.com", 443);
var ssl = new SslStream(tcpClient.getStream(), false);

If i provide custom validation callback, it always receives chain errors in 'errors' argument.

Accessing HTTPS with WebRequest API seems to work ok. But I need custom protocol over SSL. Is it possible with MonoTouch?

Best regards
Comment 1 Sebastien Pouliot 2013-06-03 11:45:19 UTC
As of today you'll need to provide your own validation callback and, on error, use the MonoTouch.Security API [1] to check if iOS trust (or not) the provided certificate.

This will be very similar to what is presently done in ServicePointManager [2] for HttpWebRequest.

[1] http://docs.go-mono.com/?link=T%3aMonoTouch.Security.SecTrust
[2] https://github.com/mono/mono/blob/master/mcs/class/System/System.Net/ServicePointManager.cs#L489
Comment 2 Ilya Skriblovsky 2013-06-04 01:58:19 UTC
I've already solved this in my code in following way:
using System.Security.Cryptography.X509Certificates;
using Mono.Security;
using MonoTouch.Security;

var syscerts = new System.Security.Cryptography.X509Certificates.X509Certificate[certificates.Count];
for (var i = 0; i < certificates.Count; i++)
    syscerts[i] = new X509Certificate(certificates[i].RawData);
var trust = new SecTrust(new X509CertificateCollection(syscerts), SecPolicy.CreateSslPolicy(false, serverName));
var result = trust.Evaluate();
return result == SecTrustResult.Unspecified || result == SecTrustResult.Proceed;

Seems to work well. But this is iOS-specific solution.
Comment 4 Sebastien Pouliot 2013-07-01 20:21:49 UTC
Fixed in 96a8500795960ae1d9e81e28095c3e2dda303342
Comment 5 Brendan Zagaeski (Xamarin Team, assistant) 2014-04-18 01:22:44 UTC
Created attachment 6609 [details]
Modified patch to allow validation without a RemoteCertificateValidationCallback

I could easily be mistaken about the correct way to fix this, but it looks like the original test scenario for this bug doesn't work at the moment. The attached patch makes two small revisions to the changes in `SslStream.BeginAuthenticateAsClient()`:


2. Re-order the lines a little so that `ServicePointManager.ChainValidationHelper.ValidateChain()` is _always_ added to the `SslClientStream.ServerCertValidation2` event handler, regardless of whether the user passes a `RemoteCertificateValidationCallback` into the `SslStream` constructor.

These two changes allow the original test scenario to run successfully.

Based on a comment from a slightly earlier commit (aaea1f82e4141d7f2193b0fe9258e84e9dc57a9f), I think these changes might be OK. To paraphrase: "Compiling only the `RemoteValidation()` method, and skipping the `LocalValidation()` method helps the linker to remove a lot of validation code that will never be used on OS X and iOS. On those platforms, the `INSIDE_SYSTEM` version of `SslClientStream` will always have a value assigned to the `ServerCertValidation2` event handler."

## Original test scenario
> var tcpClient = new TcpClient("google.com", 443);
> var ssl = new SslStream(tcpClient.GetStream(), false);
> ssl.AuthenticateAsClient("google.com");
Comment 6 Brendan Zagaeski (Xamarin Team, assistant) 2014-04-18 01:32:58 UTC
Whoops. Actually, I think the change from `MONOTOUCH` to `INSIDE_SYSTEM` doesn't make sense in this case because `System.Net.Security.SslStream` is always "inside system." That part of the patch can be ignored.

Comment 7 GouriKumari 2016-01-12 22:31:30 UTC
Moving milestone to C7.
Comment 8 Sebastien Pouliot 2016-01-14 15:09:42 UTC
-> BCL

@Martin, was this fixed as part of in your TLS refactoring ?
Comment 9 Martin Baulig 2016-01-18 16:21:24 UTC
Yeah, all that code has been reworked and fixed.

For iOS / Mac, there'll also be a new API which lets you do that SecTrust thing without providing your own callback.  This'll allow you to use a self-signed certificate and still use the system certificate validator.
Comment 10 Sebastien Pouliot 2016-01-18 16:24:39 UTC
Resolving for QA validation on C7.