TLS 1.2 not negotiated in .NET 4.7 without explicit ServicePointManager.SecurityProtocol call

C#.NetWinformsHttpwebrequesttls1.2

C# Problem Overview


I need to upgrade a .NET application to support a call to an API on a website that only supports TLS 1.2. From what I read, if the application is targeting 4.6 or higher then it will use TLS 1.2 by default.

To test I created a Windows Forms app that targets 4.7. Unfortunately it errors when I don't explicitly set ServicePointManager.SecurityProtocol. Here is the code:

HttpClient _client = new HttpClient();

var msg = new StringBuilder();

// If I uncomment the next line it works, but fails even with 4.7
// ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

var httpWebRequest = (HttpWebRequest)WebRequest.Create("https://sandbox.authorize.net");

httpWebRequest.KeepAlive = false;

try
{
    var httpWebResponse = (HttpWebResponse) httpWebRequest.GetResponse();

    msg.AppendLine("The HTTP request Headers for the first request are: ");

    foreach (var header in httpWebRequest.Headers)
    {
        msg.AppendLine(header.ToString());
    }

    ResponseTextBox.Text = msg.ToString();
   
}
catch (Exception exception)
{
   ResponseTextBox.Text = exception.Message;
 
   if (exception.InnerException != null)
   {
       ResponseTextBox.Text += Environment.NewLine + @"  ->" + exception.InnerException.Message;

       if (exception.InnerException.InnerException != null)
       {
            ResponseTextBox.Text += Environment.NewLine + @"     ->" + exception.InnerException.InnerException.Message;
       }
   }
}

If you uncomment out the following line:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

it works. This isn't a good solution since it hard codes what TLS version to use, so it wouldn't use TLS 1.3 in future.

What else do I need to do to get it work without having this line. I'm testing from a Window 10 machine with 4.7 installed.

Update

I tried a test with HttpClient and had the same results, I had to explicitly set SecurityProtocol.

Code:

var msg = new StringBuilder();

// Need to uncomment code below for TLS 1.2 to be used
// ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

try
{
   var response = await _client.GetAsync(@"https://sandbox.authorize.net");

   msg.AppendLine("response.IsSuccessStatusCode : " + response.IsSuccessStatusCode);

   msg.AppendLine(await response.Content.ReadAsStringAsync());

   textBox.Text = msg.ToString();
  }
   
  catch (Exception exception)
  {
      textBox.Text = exception.Message;

      if (exception.InnerException != null)
      {
          textBox.Text += Environment.NewLine + @"  ->" + exception.InnerException.Message;
      }
   }

C# Solutions


Solution 1 - C#

I had the same issue (Windows 10 and SSL3 / TLS only... not System Default) with a legacy app targeting 4.7.2. My issue was that during the upgrade process over the years we never added in the targetFramework to the system.web > httpRuntime element (Note: it did exist on system.web > compilation element). Before taking bigger steps, ensure your system.web looks something like the following:

<system.web>
    <compilation targetFramework="4.7.2"></compilation>
    <httpRuntime targetFramework="4.7.2" />
</system.web>

In the above example, swap 4.7.2 for whatever version of the framework you are currently using that is >= 4.7.

Solution 2 - C#

Starting with apps that target the .NET Framework 4.7, the default value of the ServicePointManager.SecurityProtocol property is SecurityProtocolType.SystemDefault.

This change allows .NET Framework networking APIs based on SslStream (such as FTP, HTTPS, and SMTP) to inherit the default security protocols from the operating system instead of using hard-coded values defined by the .NET Framework.

That's the reason of the new behaviour you experienced and the need of the new configuration:

<runtime>
   <AppContextSwitchOverrides value="Switch.System.ServiceModel.DisableUsingServicePointManagerSecurityProtocols=false;Switch.System.Net.DontEnableSchUseStrongCrypto=false" /> 
</runtime>

See here and here

Update (useful info)

Keep in mind, best security practices suggest to update your IIS configuration disabling, time by time, old protocols and ciphers key (e.g. TLS 1.0, 1.1). See Setup Microsoft Windows or IIS for SSL Perfect Forward Secrecy and TLS 1.2 for very interesting info.

If you follow this practice, you don't need to set the configuration above (as MS suggests), because your Win server / IIS is already well configured.

Of course, this is possible only if you have access to the server with proper grants.

Solution 3 - C#

As an alternative to Nick Y's answer, I discovered that on Windows 7 using .NET 4.7+, I needed to enable these registry settings in order for the Microsoft Secure Channel (Schannel) package to properly send TLS1.1 and TLS1.2.

This allows the .NET client to continue to have System.Net.ServicePointManager.SecurityProtocol set to SystemDefault and get TLS 1.1 and 1.2 on a Windows 7 computer.

Using the SystemDefault option allows .NET to defer the selection of protocols to the OS. This means that when Microsoft releases hotfixes to the OS to disable insecure protocols or enables support for new ones in their native SCHANNEL library, .NET framework apps running will automatically get this new behavior.

Here are the registry entries:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client]
"DisabledByDefault"=dword:00000000

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client]
"DisabledByDefault"=dword:00000000

Solution 4 - C#

I've found one solution. It doesn't answer the question about why TLS 1.2 isn't being used by default on Win10 with .NET 4.7, but it does allow me not to have to set ServicePointManager.SecurityProtocol.

The solution that worked from both my 4.5.2 and 4.7 test apps is to add the following to app.config:

<AppContextSwitchOverrides value="Switch.System.Net.DontEnableSchUseStrongCrypto=false"/>

Here the whole app.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
    </startup>
    <runtime>
      <AppContextSwitchOverrides value="Switch.System.Net.DontEnableSchUseStrongCrypto=false"/>
    </runtime>
</configuration>

Solution 5 - C#

I am on Windows 7 and .NET 4.7.1

The recommendation to use Switch.System.ServiceModel.DisableUsingServicePointManagerSecurityProtocols and Switch.System.Net.DontEnableSchUseStrongCrypto mentioned in two other answers did not work in my project and OP's code was failing too.

Reviewing source code for ServicePointManager and LocalAppContextSwitches I came across another config setting which worked.

<runtime>
  <AppContextSwitchOverrides value="Switch.System.Net.DontEnableSystemDefaultTlsVersions=true" />
</runtime>

Solution 6 - C#

I had the same problem for a Word VSTO add-in project which initially developed using .NET framework 4.5.2 and then upgraded into 4.7. Tried all the solutions in here, but none of them worked for me. Finally found this article.

For those who are having the same problem as me for VSTO add-in projects, add these code in the ThisAddIn.cs class, it worked for me.

AppContext.SetSwitch("Switch.System.ServiceModel.DisableUsingServicePointManagerSecurityProtocols", false);
AppContext.SetSwitch("Switch.System.Net.DontEnableSchUseStrongCrypto", false);

Solution 7 - C#

Windows 7 has TLS1.2 and TLS1.1 disabled by default. Enabling it in IE has no effect for other applications.

You should enable it by registry: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client] "DisabledByDefault"=dword:00000000

Very useful article with script to set as only TLS1.2

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJoshView Question on Stackoverflow
Solution 1 - C#scottheckelView Answer on Stackoverflow
Solution 2 - C#GianpieroView Answer on Stackoverflow
Solution 3 - C#MerickOWAView Answer on Stackoverflow
Solution 4 - C#JoshView Answer on Stackoverflow
Solution 5 - C#Nick YView Answer on Stackoverflow
Solution 6 - C#ApsSanjView Answer on Stackoverflow
Solution 7 - C#SÅ‚awomirMView Answer on Stackoverflow