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

Josh picture Josh · Jun 26, 2017 · Viewed 32.4k times · Source

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;
      }
   }

Answer

Gianpiero picture Gianpiero · Oct 7, 2017

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.