20
votes

I have the following C# code, constructing an https call with a custom certificate. When using Tls 1.1, the call works fine. When using Tls 1.2 the call breaks. I using curl, using tls 1.2 works fine as well.

C# Code:

X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import("C:\\SomePath\\MyCertificate.pfx", "MyPassword", X509KeyStorageFlags.PersistKeySet);
var cert = collection[0];

ServicePointManager.SecurityProtocol = ...;

ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) => true;
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true;
handler.ClientCertificates.Add(cert);

var content = new ByteArrayContent(Encoding.GetEncoding("latin1").GetBytes("Hello world"));
HttpClient client = new HttpClient(handler);
var resp = client.PostAsync(requestUri: url, content: content).Result.Content.ReadAsStringAsync().Result;

Works with:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11;

Error with:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

.Net error message: SocketException: An existing connection was forcibly closed by the remote host

.Net version : 4.7.1

OS: Windows 10 version 1703 (supported cipher list: https://msdn.microsoft.com/en-us/library/windows/desktop/mt808163(v=vs.85).aspx) - and the server specifies TLS_RSA_WITH_AES_256_GCM_SHA384 to be used, which is among the supported ciphers.

In wireshark I can see that with the working calls (C#/Tls 1.1 and Curl Tls 1.2) the certificate is being sent to the server. Here is the wireshark dump for the C# tls 1.1 call:

Wireshark dump - Csharp tls 1.1

However, also in wireshark, I can see that with C#/Tls 1.2 there is no certificate being sent from the client to the server. Here is the wireshark dump for the C# tls 1.2 call:

enter image description here

Can anyone see what I am missing here?

UPDATE

It seems the certificate has an md5 signature which is not supported by Schannel in windows in combination with tls 1.2. Our vendor has created another certificate to us as a solution.

I came across this random thread that discusses the issue: https://community.qualys.com/thread/15498

3
I had similar issue with .Net Core 2.0 client/server application. I solved by forcing TLS 1.2 on server. This way, client negotiate protocol and certificate properly. You might try to connect to a server which is forced to TLS 1.2 only and see if you have same behaviour with .NET Framework.Ghigo
well changing this on the server is not really an option here as the server belongs to a vendor. And it doesn’t matter if I add tls 1.1 along. As long as tls 1.2 is just among the allowed protocols, this issue happens. I need to know why .net behaves this way when curl works just fine. And I need tls 1.2 enabled because this is a static setting and other connections depend on tls 1.2 in the same application. If I could turn of tls 1.3 for this single call, it would do. But setting the protocols on the handler object throws an exception telling that this is not possible in the current env.Stephan Møller
Looks a lot like a duplicate of this question stackoverflow.com/questions/44751179/…Robbert Draaisma
Well I dont think it is a duplicate. First of all, your link shows a thread describing an issue where tls 1.2 is not working unless explicitly set - my issue is kind the opposite: https not working WHEN tls 1.2 enabled. Second, the fix described using app.config did not help anything.Stephan Møller
Check if you have any restrictions on cipher suites at the protocol level, like so: technet.microsoft.com/en-us/library/…zaitsman

3 Answers

2
votes

You are right on the root cause of this problem: By default, schannel-based clients offer SHA1, SHA256, SHA384 and SHA512 (on Win10/Server 2016). So TLS 1.2 servers are not supposed to send their MD5 certs to these clients.

The client (HttpClient) does not list MD5 in the signature_algorithms extension, so the TLS 1.2 handshake fails. The fix is to use a secure server cert.

0
votes

I believe this code is masking some type of certificate error by always blindly returning true:

handler.ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true;

I recommend you have a function to truly analyze the results of arg4. That is your SSL policy errors. Log them and you will get your answer. In my example, I write to the console, but you can write to the trace, or a file. You'll get a number which will be associated a value for the SslPolicyErrors enumeration. Based on the results you might need to check your arg3, which is your chain.

ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => {

SslPolicyErrors errs = sslPolicyErrors;
Console.WriteLine("Policy Errors " + sslPolicyErrors.ToString());           
return true;};
0
votes

Shouldn't you specified the handler's SslProtocols property?

Try adding this line after hander definition:

handler.SslProtocols = SslProtocols.Tls12;