Overview
A .Net Core 2.0 application running on OSX High Sierra cannot connect to a server using SSLStream with certificate revocation checking enabled. The server’s certificate has extensions for CRLs but not OCSP. If one builds this application on Linux or Windows, the application will connect (to the same server) without any problems. Additionally, using the same certificate chain to host a web server via openssl s_server, Safari is able to connect on OSX without any problems.
Exception details
AuthenticationException message: Remote certificate is invalid according to the validation procedure.
The sole X509ChainStatus in the X509Chain is on the leaf certificate: "An incomplete certificate revocation check has occurred."
The corresponding Apple error code is errSecIncompleteCertRevocationCheck (-67635) (https://developer.apple.com/documentation/security/errsecincompletecertrevocationcheck).
Workaround
Interesting observations
Additional thoughts
I am admittedly a little uncertain if the problem is on Apple's end: i.e. the Apple Security Framework has additional restrictions (compared to CNG and OpenSSL) on what it considers to be a well-formed chain. My only hint that indicates otherwise is that that Safari is able to connect a webserver utilizing the same set of certificates.
Reproduction steps
Create a chain of certificates that mimics a certificate signed by DigiCert SHA2 Secure Server CA.
The following are the steps I used:
On Windows and Linux (after installing the root CA certificate), the
test app will connect without any problems, whereas the application will fail to connect on OSX.
However, as previously noted, if you use the same certificate to host a webserver (e.g.
"openssl s_server -port 44330 -key keyA.pem -cert certA.pem -CAfile intermediatecert.pem -www"),
then Safari will be able to connect to that webserver without any problems.
I have reproduced this with the app targeting .NET Core 2.0 as well as 2.1 preview, using .NET Core SDK 2.1.300-preview1-008174. This also occurs when using SDK 2.1.104 (targeting .NET Core 2.0 only).
I've included my test app below for convenience:
// Program.cs using System; using Ssl; namespace crl_test_app { class Program { static void Main(string[] args) { string serverCertificateName = "tyche"; string machineName = "tyche"; while (true) { Console.WriteLine("Press enter to connect and send a message."); Console.ReadLine(); Console.WriteLine("Attempting to connect..."); SslTcpClient.RunClient(machineName, serverCertificateName); } } } } // Ssl.cs using System; using System.IO; using System.Linq; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; namespace Ssl { public class SslTcpClient { // Purely for debugging purposes, not needed public static bool ValidateServerCertificate( object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { var errors = chain.ChainStatus.Aggregate("", (str, x) => str + "," + x.Status); Console.WriteLine($"SslPolicyError: {sslPolicyErrors} {errors}"); foreach (var item in chain.ChainElements) foreach (var elemStatus in item.ChainElementStatus) Console.WriteLine(item.Certificate.Subject + "->" + elemStatus.StatusInformation); return true; } public static void RunClient(string machineName, string serverName) { var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); socket.Connect(serverName, 44330); Console.WriteLine("Client connected."); using (var stream = new NetworkStream(socket)) { var sslStream = new SslStream(innerStream: stream, leaveInnerStreamOpen: false, userCertificateValidationCallback: ValidateServerCertificate); try { sslStream.AuthenticateAsClientAsync(serverName, new X509Certificate2Collection(), SslProtocols.Tls12, checkCertificateRevocation: true).GetAwaiter().GetResult(); } catch (AuthenticationException e) { Console.WriteLine("Exception: {0}", e.Message); if (e.InnerException != null) Console.WriteLine("Inner exception: {0}", e.InnerException.Message); Console.WriteLine("Authentication failed - closing the connection."); return; } var w = new StreamWriter(sslStream); w.WriteLine("Hello from the client."); w.Flush(); var serverMessage = ReadLine(new StreamReader(sslStream)); Console.WriteLine("Server says: {0}", serverMessage); Console.WriteLine("Press enter to close the connection."); Console.ReadLine(); } socket.Dispose(); Console.WriteLine("Client closed."); } static string ReadLine(StreamReader reader) => reader.ReadLine(); static void SendMessage(SslStream sslStream, string s="Hello from the client.") => sslStream.Write(Encoding.UTF8.GetBytes(s)); } }
[edit: whitespace, typos]
[EDIT] Add C# syntax highlighting by @karelz
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4