Disabling cryptographic protocols for PCI compliance (focused on SSL 3.0 and TLS 1.0)

PCI DSS (Payment Card Industry, Data Security Standard) requires that cryptographic protocols with known vulnerabilities, must be disabled (recently introduced in revision 3.1). This includes SSL 2.0, SSL 3.0 and TLS 1.0, meaning that after June of 2016, any environment supporting those protocols will automatically fail a PCI audit. At the time of this writing, only TLS 1.1 and TLS 1.2 should be enabled (TLS 1.3 still in draft phase).

In a Windows environment, cryptographic protocols can be managed in the registry. By default, all common protocols (from SSL 2.0 and above) are enabled for incoming and outgoing connections, however you might not be able to find them in the registry, since they are hidden by default. That just means we need to check if the registry entry exist, before applying any modification. If it doesn't exist, it must be created first. Additionally for each protocol, there are 'Server' and 'Client' settings. To simplify it, they can be interpreted as 'incoming' (server) and 'outgoing' (client) connection settings. E.g. your Web-server can reject incoming TLS 1.0, but allow outgoing TLS 1.0 connections. This configuration makes sense if your web application needs to fetch information from an external resource using TLS 1.0, but wants to blocks any incoming TLS 1.0 connection.

For compatibility reasons, multiple protocols can be enabled. On encrypted connections, before data starts flowing between client and server, they must agree on what protocol should be used. Today, any modern browser will automatically handle this negotiation step, retrying all available protocols until they find one that both can communicate over. However that doesn't mean that all applications/frameworks will do the same. .Net in particular (by default in version 4.5), will try to use TLS 1.0 (if you have it enabled on the client) and closes the connection if the server doesn't support it. If you're trying to make a HTTPS request using .Net (e.g. using HttpClient) you could encounter the following error: .Net HttpClient HTTPS connection failure

It simply states that if the server can't 'speak' in TLS 1.0, the connection can't be established. Why the framework doesn't try to negotiate to use other protocol, only Microsoft can tell, I'm sure they have a good reason, for now just be aware of that. If you want to force .Net to retry the connection with different protocols, simply add the following statement before making the request.

System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12;

If you're configuring the server, use the following PowerShell script to fully disable SSL 2.0 and SSL 3.0; enable TLS 1.1 and TLS 1.2; block incoming but allow outgoing TLS 1.0 connections. You will have to reboot the machine to have this changes applied.

$protocols = @{
  'SSL 2.0'= @{
    'Server-Enabled' = $false
    'Client-Enabled' = $false
  }
  'SSL 3.0'= @{
    'Server-Enabled' = $false
    'Client-Enabled' = $false
  }
  'TLS 1.0'= @{
    'Server-Enabled' = $false
    'Client-Enabled' = $true
  }
  'TLS 1.1'= @{
    'Server-Enabled' = $true
    'Client-Enabled' = $true
  }
  'TLS 1.2'= @{
    'Server-Enabled' = $true
    'Client-Enabled' = $true
  }
}


$protocols.Keys | ForEach-Object {
  
  Write-Output "Configuring '$_'"

  # create registry entries if they don't exist
  $rootPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$_"
  if(-not (Test-Path $rootPath)) {
    New-Item $rootPath
  }

  $serverPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$_\Server"
  if(-not (Test-Path $serverPath)) {
    New-Item $serverPath

    New-ItemProperty -Path $serverPath -Name 'Enabled' -Value 4294967295 -PropertyType 'DWord'
    New-ItemProperty -Path $serverPath -Name 'DisabledByDefault' -Value 0 -PropertyType 'DWord'
  }

  $clientPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$_\Client"
  if(-not (Test-Path $clientPath)) {
    New-Item $clientPath
    
    New-ItemProperty -Path $clientPath -Name 'Enabled' -Value 4294967295 -PropertyType 'DWord'
    New-ItemProperty -Path $clientPath -Name 'DisabledByDefault' -Value 0 -PropertyType 'DWord'
  }
  
  # set server settings
  if($protocols[$_]['Server-Enabled']) {
    Set-ItemProperty -Path $serverPath -Name 'Enabled' -Value 4294967295
    Set-ItemProperty -Path $serverPath -Name 'DisabledByDefault' -Value 0
  } else {
    Set-ItemProperty -Path $serverPath -Name 'Enabled' -Value 0
    Set-ItemProperty -Path $serverPath -Name 'DisabledByDefault' -Value 1
  }
  
  # set client settings
  if($protocols[$_]['Client-Enabled']) {
    Set-ItemProperty -Path $clientPath -Name 'Enabled' -Value 4294967295
    Set-ItemProperty -Path $clientPath -Name 'DisabledByDefault' -Value 0
  } else {
    Set-ItemProperty -Path $clientPath -Name 'Enabled' -Value 0
    Set-ItemProperty -Path $clientPath -Name 'DisabledByDefault' -Value 1
  }
} 

If you want to test what protocols are enabled, use the following console application code. Just create a empty .Net 4.5 console application, keep in mind that you might need to add a reference to System.Net.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Net.Http;

namespace Playground
{
  class Program
  {
    static string _httpsTestUrl;

    static void Main(string[] args)
    {
      Console.ForegroundColor = ConsoleColor.Green;
      _httpsTestUrl = "https://";
      Console.Write("'enter test Url': https://");
      _httpsTestUrl += Console.ReadLine();
      Console.WriteLine();

      Task.Run(async delegate
      {
        await RunTests().ConfigureAwait(false);
      }).Wait();

      Console.WriteLine("\n\nPress any key to exit!");
      Console.ReadKey();
    }

    static async Task RunTests()
    {
      await PerformRequest(SecurityProtocolType.Ssl3);
      await PerformRequest(SecurityProtocolType.Tls);
      await PerformRequest(SecurityProtocolType.Tls11);
      await PerformRequest(SecurityProtocolType.Tls12);
    }

    static async Task PerformRequest(SecurityProtocolType protocol)
    {
      string protocolName = Enum.GetName(typeof(SecurityProtocolType), protocol);

      try
      {
        using (HttpClient httpClient = new HttpClient())
        {
          // sets the protocol to be used
          ServicePointManager.SecurityProtocol = protocol;
          // performs the request
          HttpResponseMessage response = await httpClient.GetAsync(_httpsTestUrl);
          Console.ForegroundColor = ConsoleColor.White;
          Console.WriteLine(string.Format("'{0}': test passed!", protocolName));
        }
      }
      catch (Exception ex)
      {
        Console.ForegroundColor = ConsoleColor.White;
        Console.WriteLine(string.Format("'{0}': test failed!", protocolName));

        List<string> errors = new List<string>();
        do
        {
          errors.Add(ex.Message);
          ex = ex.InnerException;
        } while (ex != null);

        string errMessage = errors.Distinct().Aggregate((x, y) => string.Format("{0}\n   >>> {1}", x, y));

        // write the error message to the console
        Console.ForegroundColor = ConsoleColor.Gray;
        Console.WriteLine(string.Format("   >>> {0}", errMessage));
      }
    }
  }
} 

Start the application, enter the URL of the server you want to test and let the application do the rest. test TLS .Net console application screenshot

If you are looking for more detailed information, can you use nmap. Download the command line for Windows from https://nmap.org/dist/nmap-7.00-win32.zip or check another download options at https://nmap.org/download.html Run nmap executable, use 'ssl-enum-ciphers' script and specify the ports and Url you want to test.

.\nmap.exe --script ssl-enum-ciphers -p 443 google.com 

Here's an example: nmap ssl enum cipher screenshot

This article just focus on insecure cryptographic protocols mentioned in the latest revision of PCI DSS. For more information about PCI DSS, please download the full document from https://www.pcisecuritystandards.org/documents/PCI_DSS_v3-1.pdf