Does .NET 4.x support TLS Ciphers outside of SCHANNEL?

Matias Kiviniemi 0 Reputation points
2023-06-12T07:40:14.62+00:00

My assumption has been that .NET relies on SCHANNEL for TLS/Cipher support, i.e. the list of supported cipher is the list of ciphers of the Windows version. We have some 2012 R2 servers which should be Win8.1 for Cipher support and lack many of the preferred GCM-modes.

 

But when testing on 2012 R2 with a server that only supports ECDHE-RSA-AES128-GCM-SHA256  and ECDHE-RSA-AES256-GCM-SHA384 that are not supported 2012 R2 (https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-8-1) I still managed to connect. This sounds good for me (we want this to work), but I'm not sure why it's happening and/or if it's something I can trust.

 

I Customized SslStream example to a simple command line app tester that makes a connection to a host, prints out SslStream-parameters and makes a HTTP HEAD-request if anyone wants to try this behavior. On 2012 R2 I get (44550 is ECDHE)

Authenticated using:

  • CipherAlgorithm: Aes256(256)
  • KeyExchangeAlgorithm: 44550(256)
  • HashAlgorithm: Sha384(0)

 

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace TlsTester
{
	public class SslTcpClient
	{
		private static string ServerName = "";

		// The following method is invoked by the RemoteCertificateValidationDelegate.
		public static bool ValidateServerCertificate(
			  object sender,
			  X509Certificate certificate,
			  X509Chain chain,
			  SslPolicyErrors sslPolicyErrors)
		{
			if (sslPolicyErrors == SslPolicyErrors.None)
				return true;

			Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
			Console.WriteLine("Certificate Subject: {0}", certificate.Subject);
			Console.WriteLine("Certificate Issuer: {0}", certificate.Issuer);
			Console.WriteLine("Certificate Handle: {0}", certificate.Handle);

			// Allow self signed certificates with name matching second argument
			return certificate.Subject.Contains("CN="+ServerName);
		}
		public static void RunClient(string machineName, string serverName)
		{
			ServerName = serverName;
			// Create a TCP/IP client socket.
			// machineName is the host running the server application.
			TcpClient client = new TcpClient(machineName, 443);
			Console.WriteLine("Client connected.");
			// Create an SSL stream that will close the client's stream.
			SslStream sslStream = new SslStream(
				client.GetStream(),
				false,
				new RemoteCertificateValidationCallback(ValidateServerCertificate),
				null
				);
			// Output the SslStream-options that were negotiated
			try
			{
				sslStream.AuthenticateAsClient(serverName);
				Console.WriteLine("Authenticated using:");
				Console.WriteLine(" * CipherAlgorithm:\t\t" + sslStream.CipherAlgorithm.ToString() + "(" + sslStream.CipherStrength.ToString() + ")");
				Console.WriteLine(" * KeyExchangeAlgorithm:\t" + sslStream.KeyExchangeAlgorithm.ToString() + "(" + sslStream.KeyExchangeStrength.ToString() + ")");
				Console.WriteLine(" * HashAlgorithm:\t\t" + sslStream.HashAlgorithm.ToString() + "(" + sslStream.HashStrength.ToString() + ")");
			}
			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.");
				client.Close();
				return;
			}
			// send HTTP HEAD request
			byte[] message = Encoding.UTF8.GetBytes("HEAD / HTTP/1.1\r\nHost: " + machineName + "\r\nAccept: */*\r\n\r\n");
			// Send hello message to the server. 
			sslStream.Write(message);
			sslStream.Flush();
			// Read message from the server.
			string serverMessage = ReadMessage(sslStream);
			Console.WriteLine("Server says: {0}", serverMessage);
			// Close the client connection.
			client.Close();
			Console.WriteLine("Client closed.");
		}
		static string ReadMessage(SslStream sslStream)
		{
			// Read the  message sent by the server.
			// The end of the message is signaled using the
			// "<EOF>" marker.
			byte[] buffer = new byte[2048];
			StringBuilder messageData = new StringBuilder();
			int bytes = -1;
			do
			{
				bytes = sslStream.Read(buffer, 0, buffer.Length);

				Decoder decoder = Encoding.UTF8.GetDecoder();
				char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
				decoder.GetChars(buffer, 0, bytes, chars, 0);
				messageData.Append(chars);
				// Check for EOF.
				if (messageData.ToString().IndexOf("\r\n\r\n") != -1)
				{
					break;
				}
			} while (bytes != 0);

			return messageData.ToString();
		}
		private static void DisplayUsage()
		{
			Console.WriteLine("To start the client specify:");
			Console.WriteLine("TlsTester ServerName [CertificateName]");
			Environment.Exit(1);
		}
		public static int Main(string[] args)
		{
			string serverCertificateName = null;
			string machineName = null;
			if (args == null || args.Length < 1)
			{
				DisplayUsage();
			}
			// User can specify the machine name and server name.
			// Server name must match the name on the server's certificate. 
			machineName = args[0];
			if (args.Length < 2)
			{
				serverCertificateName = machineName;
			}
			else
			{
				serverCertificateName = args[1];
			}
			SslTcpClient.RunClient(machineName, serverCertificateName);
			return 0;
		}
	}
}

Developer technologies | .NET | Other
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. abbodi86 4,036 Reputation points
    2023-06-12T20:34:34.4533333+00:00

    Yes, .NET Framework uses SChannel SSPI

    are you sure the server uses that ciphers only?

    ECDHE-RSA-AES and ECDHE-ECDSA-AES ciphers are very similar, the latter is supported in Win8.1 / 2012 R2

    https://datatracker.ietf.org/doc/html/rfc5289#section-3.2

    your code confirm that ECDHE-RSA-AES ciphers don't work for Win8.1 (learn.microsoft.com is clear example)

    Screenshot (262)

    Screenshot (263)

    p.s.

    https://learn.microsoft.com/en-us/answers/questions/221066/specific-powershell-tls1-2-request-wont-work-windo

    https://support.vertigis.com/hc/en-us/articles/11461032941586-Windows-Server-2012-R2-and-Advanced-TLS-1-2-Cipher-Suites

    1 person found this answer helpful.

  2. Matias Kiviniemi 0 Reputation points
    2023-06-13T06:42:11.13+00:00

    Reposting my comment above and making it the accepted answer as this is more informative to most, thanky @abbodi86 for help!

    Interesting, thanks! I have the the same experience that Invoke-Webrequest fails. And I've tried to verify that the test server only has the ciphers ECDHE-RSA-AES128-GCM-SHA256 and ECDHE-RSA-AES256-GCM-SHA384 by using curl with --ciphers to force a single cipher and should be those two.

    Very good idea to run the app through powershell as you can make direct comparison of the runtime and turns out you can get different results! When calling our test server (it has an IP-whitelist so no publically available) powershell-version fails and compiled binary succeeds.

    PS C:\temp> [TlsTester.SslTcpClient]::Main(@("aa.bb.cc.dd", "selfsigned-cert-name"))
    Client connected.
    Exception: A call to SSPI failed, see inner exception.
    Inner exception: The message received was unexpected or badly formatted
    Authentication failed - closing the connection.
    
    PS C:\temp> .\TlsTester.exe "aa.bb.cc.dd", "selfsigned-cert-name"
    Client connected.
    Certificate error: RemoteCertificateChainErrors
    Certificate Subject: CN=selfsigned-cert-name, OU=Test, L=xx, O=yy, C=FI
    Certificate Issuer: CN=selfsigned-cert-name, O=yy, C=FI
    Certificate Handle: 8416496
    Authenticated using:
     * CipherAlgorithm:		Aes256(256)
     * KeyExchangeAlgorithm:	44550(256)
     * HashAlgorithm:		Sha384(0)
    

    When I tried it with learn.microsoft.com, both fail.

    PS C:\temp> [TlsTester.SslTcpClient]::Main(@("learn.microsoft.com"))
    Client connected.
    Exception: A call to SSPI failed, see inner exception.
    Inner exception: The function requested is not supported
    Authentication failed - closing the connection.
    
    PS C:\temp> .\TlsTester.exe learn.microsoft.com
    Client connected.
    Exception: A call to SSPI failed, see inner exception.
    Inner exception: The message received was unexpected or badly formatted
    Authentication failed - closing the connection.
    

    I tested that server with my curl tool, it does not support ECDHE-RSA-AES128-GCM-SHA256 and ECDHE-RSA-AES256-GCM-SHA384 but forces a TLS 1.3 with TLS_AES_256_GCM_SHA384 (this seems to be the only supported one).

    OK, CIPHER FOUND: Cipher selection: ECDHE-RSA-AES128-GCM-SHA256
    FAIL, CIPHER CHANGED: SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
    OK, CIPHER FOUND: * Cipher selection: ECDHE-RSA-AES256-GCM-SHA384
    FAIL, CIPHER CHANGED: SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
    

    I think the reason is that there are multiple versions of schannel.dll (several actually) and powershell invokes one and .NET-runtime another. There's still a ton of uncertainty how/where/why those are distributed. But optimistically it might be possible backport TLS-cipher support by using a different schannel.dll (with ones own responsibility).

    C:\>dir schannel.dll /s
     Volume in drive C has no label.
     Volume Serial Number is 10D8-D7C2
    
     Directory of C:\Windows\System32
    
    27.04.2023  08:49           434 176 schannel.dll
                   1 File(s)        434 176 bytes
    
     Directory of C:\Windows\SysWOW64
    
    27.04.2023  08:40           359 936 schannel.dll
                   1 File(s)        359 936 bytes
    
     Directory of C:\Windows\WinSxS\amd64_microsoft-windows-security-schannel_****
    
    26.04.2021  02:21            66 858 schannel.dll
    24.01.2022  06:15             1 520 schannel.dll
    01.01.2022  07:21           435 712 schannel.dll
    21.02.2022  03:50                12 schannel.dll
    21.03.2022  03:59                12 schannel.dll
    25.04.2022  02:12            58 492 schannel.dll
    22.05.2023  04:19            65 465 schannel.dll
    22.05.2023  04:15           435 200 schannel.dll
    22.05.2023  04:19            42 384 schannel.dll
    27.04.2023  08:49           434 176 schannel.dll
    26.04.2021  02:24            52 590 schannel.dll
    24.01.2022  06:19             1 100 schannel.dll
    01.01.2022  07:07           359 424 schannel.dll
    21.02.2022  03:52                12 schannel.dll
    21.03.2022  04:02            38 950 schannel.dll
    25.04.2022  02:13            46 335 schannel.dll
    22.05.2023  04:22            53 137 schannel.dll
    22.05.2023  04:15           359 936 schannel.dll
    22.05.2023  04:22            45 364 schannel.dll
    27.04.2023  08:40           359 936 schannel.dll
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.