Bug 19141 - SslStream.AuthenticateAsClient() does not invoke RemoteCertificateValidationCallback on every call, unlike .NET
Summary: SslStream.AuthenticateAsClient() does not invoke RemoteCertificateValidationC...
Alias: None
Product: Class Libraries
Classification: Mono
Component: Mono.Security ()
Version: master
Hardware: PC Mac OS
: --- normal
Target Milestone: Untriaged
Assignee: Sebastien Pouliot
Depends on:
Reported: 2014-04-17 19:29 UTC by Brendan Zagaeski (Xamarin Team, assistant)
Modified: 2014-05-29 21:13 UTC (History)
2 users (show)

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

Test and possible patch (7.82 KB, patch)
2014-04-17 19:29 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 for Bug 19141 on GitHub or Developer Community if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: GitHub Markdown or Developer Community HTML
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:

Description Brendan Zagaeski (Xamarin Team, assistant) 2014-04-17 19:29:04 UTC
Created attachment 6608 [details]
Test and possible patch

## Test case (also included as a unit test in the attached patch)

If you run the following lines twice back-to-back in Mono, the `callbackCount` will only be 1, assuming the TLS session ID stays the same for the two connections. Every time I ran the test on .NET, `callbackCount` was 2. 

> var callbackCount = 0;
> var tcpClient = new TcpClient ("localhost", 443);
> var ssl = new SslStream (tcpClient.GetStream (), false, (sender, certificate, chain, sslPolicyErrors) => {
> 	callbackCount += 1;
> 	return true;
> });
> ssl.AuthenticateAsClient ("localhost");

## Additional information

On both Mono and .NET, if the TLS session ID stays the same between the two calls to `AuthenticateAsClient()`, then an abbreviated TLS handshake is performed [1]. In this abbreviated handshake, the server does _not_ re-send its certificates.

On Mono, the incoming server certificates are handled by the `TlsServerCertificate` class, and `RemoteCertificateValidationCallback` is only called when `TlsServerCertificate.Process()` is run. So when the second call to `AuthenticateAsClient()` uses an abbreviated handshake, it creates _no_ `TlsServerCertificate` instances, and `RemoteCertificateValidationCallback` is never run.

> [1] https://github.com/mono/mono/blob/0af984a39b46915d4ce1762edb4bdda8c41faef7/mcs/class/Mono.Security/Mono.Security.Protocol.Tls.Handshake.Client/TlsServerHello.cs#L103-L104

## Attached patch

The attached patch ensures that `callbackCount` will always end with a value of 2 on Mono. It does this by:

1. Adding the server certificates to the `ClientSessionCache`. That way the certificates received during the first call to `AuthenticateAsClient()` are available during second call.

2. Creating a "dummy" `TlsServerCertificate` message using the cached certificates.

3. Asking the dummy message to validate the certificates as normal.

The unit test currently assumes that "localhost" is set up to handle TLS requests. If needed, replace localhost with a remote host that accepts TLS connections.
Comment 1 Sebastien Pouliot 2014-05-28 17:12:52 UTC
Looks similar (likely identical) to bug #17326. I'll review this as well.
Comment 2 Sebastien Pouliot 2014-05-29 08:31:41 UTC
That behavior exisst because of the handshake cache. It can be disable with setting the environment variable "MONO_TLS_SESSION_CACHE_TIMEOUT" to "0" or adding this line to your application:

    Environment.SetEnvironmentVariable ("MONO_TLS_SESSION_CACHE_TIMEOUT", "0");
Comment 4 Brendan Zagaeski (Xamarin Team, assistant) 2014-05-29 21:13:36 UTC
All sounds good! Setting the "MONO_TLS_SESSION_CACHE_TIMEOUT" environment variable to "0" does indeed produce the desired outcome.

From the other bug, it sounds like a key issue is that even .NET does not promise that this callback must be called on every handshake attempt. So .NET could conceivably change the behavior in the future to cache the validation and skip the callback on the second attempt, just like Mono. Given these considerations, it might be best to keep the behavior in Mono unchanged.

Thanks for the information!