Microsoft’s .NET framework is a collection of tools and libraries accessible from various “.NET Programming Languages” used by developers to build applications on the Windows Platform. ASP.NET, which runs natively on IIS (Microsoft’s web server bundled with Windows Server), Visual Basic .NET, C# .NET and Windows Powershell are all examples of languages which can natively take advantage of the abstraction .NET provides. Languages which aren’t .NET languages must rely instead on the Windows API directly, which is tedious and inflexible.
- SSL/TLS in .NET
- Supported Ciphers
- Deferring to SCHANNEL
- Finding an application’s .NET version and architecture
- Backwards Compatibility and .NET
- Determine supported SSL/TLS protocol versions
SSL/TLS in .NET
SSL/TLS in .NET has changed a lot over the years. For most of its history, by default, applications compiled against a particular version of .NET used the SSL/TLS settings derived from that version of .NET unless a particular override was applied, either globally or to its web.config or app.config (the configuration file .NET applications parse when the process starts to determine certain aspects of their behavior). More recent versions of .NET (specifically 4.7 and later) changed their default behavior, and instead rely on whatever settings are configured in SCHANNEL. SCHANNEL is Microsoft’s Operating System level SSL/TLS implementation, so falling back to its configuration settings makes a lot of sense. SCHANNEL gets its configuration from the Windows Registry, at the path:
Keys exist for each major protocol version, for example:
And under those keys two DWord values must be set to enable or disable that protocol:
In order to enable a protocol, DisabledByDefault must contain a value of 0 and Enabled must contain a value of 1.
This change requires a reboot of the machine in order to take effect.
SCHANNEL’s supported ciphers can also be modified via the registry. Underneath the Ciphers subkey located at:
A registry key can be created for ciphers of which SCHANNEL is aware. For example,
A DWORD value of Enabled can be set to 0 to explicitly disable, or 1 to explicitly enable the cipher.
When an organization configures their base server image, it is a good idea to iteratively run a tool such as Qualys against an endpoint configured with SSL/TLS and use the feedback from the tool to come up with a list of ciphers to support. Additionally, Mozilla provides an excellent and up-to-date list of current best practices. It is the opinion of the author that disabling TLS 1.2 as recommended by the “Modern” recommendation at the link below is too aggressive for most organizations, and will result in compatibility issues.
n.b. notice that the backslash character is part of the path, but the forward slash character is a literal in the subkey.
Deferring to SCHANNEL
Applications compiled against versions of .NET prior to 4.7 can be forced to defer to the SCHANNEL settings above with the following registry keys:
For 64 bit applications:
For 32 Bit Applications:
Under each registry path, create a DWord named SchUseStrongCrypto with a value of 1. Note that a reboot is required for this change to take effect.
Applications at startup will query this location to make a determination as to their desired behavior.
One scenario that is possible is for an application compiled against .NET 4.5.2 to suddenly stop working when an endpoint it connects to (as they should) disables versions of TLS lower than 1.2 on their end. .NET 4.5.2 by default does not handle TLS versions above 1.1 unless configured to defer to SCHANNEL. Notably, this famously affects ADFS on Window Server 2012 R2.
The above of course if only useful if you know what version of .NET an application is compiled against, whether the application is 32-bit or 64-bit, and what versions of the TLS protocol a remote endpoint supports. Luckily, we can determine all of those things.
Finding an application’s .NET version and architecture
Every .NET application bundles with it a .MANIFEST file which contains this information. JetBrains provides a free tool, DotPeek which makes interrogating this Manifest file a snap. Simply install DotPeek, select “Open” and navigate to the program executable. In the case of a WCF service, the executable can be determined from the properties of a service in services.msc.
Open the executable path in the DotPeek utility:
On the left, the target .NET framework is listed along with the architecture of the application.
Backwards Compatibility and .NET
One common point of confusion is that just because you install a later version of .NET on your server doesn’t mean that it changes an applications behavior. Instead, applications that are compiled against an earlier version of .NET will continue to enjoy the functions and libraries that existed point-in-time when it was compiled. This is why even if you have installed .NET 4.8, an application compiled against .NET 4.5.2 cannot take advantage of TLS 1.1 by default. There are many benefits to targeting later versions of .NET, and keeping track of workarounds like the one listed in this guide incur technical debt. It is recommended that these workarounds are employed temporarily, for example when waiting for a vendor to release an update.
How to determine supported SSL/TLS protocol versions of a remote endpoint
Tools such as NMAP allow an administrator to profile for supported versions of TLS, but OpenSSL is more ubiquitously installed. Using the commands below, based on whether the server sends a response, we can determine whether a given protocol is supported by the remote server.
openssl s_client -connect google.com:443 -tls1_3
openssl s_client -connect google.com:443 -tls1_2
openssl s_client -connect google.com:443 -tls1_1
openssl s_client -connect google.com:443 -tls1_0
n.b. On Windows Systems, either OpenSSL must exist in your system’s PATH variable, or “openssl” should be replaced with the full path to the binary in the command above.