TLS, “Transport Layer Security” is a protocol that provides privacy between 2 applications. TLS has two layers, 1) TLS Record Protocol, which provides security, and 2) TLS Handshake Protocol, which allows the apps to authenticate. TLS 1.2 is the latest release. As the specification states:
The primary goal of the TLS protocol is to provide privacy and data integrity between two communicating applications.
TLS is supported by the Microsoft .NET Framework in the following ways:
- .NET 4.0 supports TLS 1.0
- .NET 4.5+ supports TLS1.2 through a code update shown below
- .NET 4.6+ supports TLS 1.2 by default
It is important to be on higher versions of TLS as older versions are subject to exploits. If your .NET code is running older versions of TLS, and the apps you are integrating with use newer protocols, you will run into errors.
In this post we will simulate getting an error trying to use a TLS version lower than TLS 1.2, running against Dynamics 365 version 9.0 and the ways to fix it. You can read more about the Dynamics 365 update to TLS 1.2 on the Microsoft website here.
Simulating the Problem
First, create a new Console app:
Change the .NET version to 4.5.2:
Now add code. We will do something simple, like connecting to Dynamics 365 and getting the version:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Xrm.Tooling.Connector; using Microsoft.Xrm.Sdk; using Microsoft.Crm.Sdk.Messages; namespace Carl.D365TLS12 { class Program { static void Main(string[] args) { try { var connectionString = @"AuthType = Office365; Url=https://yourorg.crm.dynamics.com/;Username=yourusername;Password=yourpassword"; CrmServiceClient conn = new CrmServiceClient(connectionString); IOrganizationService service; service = (IOrganizationService)conn.OrganizationWebProxyClient != null ? (IOrganizationService)conn.OrganizationWebProxyClient : (IOrganizationService)conn.OrganizationServiceProxy; RetrieveVersionRequest versionRequest = new RetrieveVersionRequest(); RetrieveVersionResponse versionResponse = (RetrieveVersionResponse)service.Execute(versionRequest); Console.WriteLine("Microsoft Dynamics CRM version {0}.", versionResponse.Version); Console.ReadLine(); } catch (Exception ex) { Console.WriteLine("An error has occurred: " + ex.ToString()); Console.ReadLine(); } } } }
Run the app. You will see the error “Object not set to an instance of an object” and “Unable to Login to Dynamics CRM”:
If we look at the connection object, we see “Unable to Login to Dynamics CRM”:
Resolving the Problem Through Code
Add the line at the beginning:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
So the code looks like:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Xrm.Tooling.Connector; using Microsoft.Xrm.Sdk; using Microsoft.Crm.Sdk.Messages; using System.Net; namespace Carl.D365TLS12 { class Program { static void Main(string[] args) { try { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; var connectionString = @"AuthType = Office365; Url=https://yourorg.crm.dynamics.com/;Username=yourusername;Password=yourpassword"; CrmServiceClient conn = new CrmServiceClient(connectionString); IOrganizationService service; service = (IOrganizationService)conn.OrganizationWebProxyClient != null ? (IOrganizationService)conn.OrganizationWebProxyClient : (IOrganizationService)conn.OrganizationServiceProxy; RetrieveVersionRequest versionRequest = new RetrieveVersionRequest(); RetrieveVersionResponse versionResponse = (RetrieveVersionResponse)service.Execute(versionRequest); Console.WriteLine("Microsoft Dynamics CRM version {0}.", versionResponse.Version); Console.ReadLine(); } catch (Exception ex) { Console.WriteLine("An error has occurred: " + ex.ToString()); Console.ReadLine(); } } } }
When we run this, we get the correct connection and output:
Resolving the Problem Through Registry Entries
Setting your machine to use strong cryptography can be done with the following PowerShell command.
64-Bit:
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
32-Bit:
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
After doing this, your code will work as before without any updates.
Resolving by Updating the .NET Version
If you change your app to use .NET 4.6 and above, TLS 1.2 will be used by default and your code will work as expected.
I AM SPENDING MORE TIME THESE DAYS CREATING YOUTUBE VIDEOS TO HELP PEOPLE LEARN THE MICROSOFT POWER PLATFORM.
IF YOU WOULD LIKE TO SEE HOW I BUILD APPS, OR FIND SOMETHING USEFUL READING MY BLOG, I WOULD REALLY APPRECIATE YOU SUBSCRIBING TO MY YOUTUBE CHANNEL.
THANK YOU, AND LET'S KEEP LEARNING TOGETHER.
CARL
[…] TLS 1.2 requirement). You can read more about that is very important here and this other link was useful […]
[…] https://carldesouza.com/dynamics-365-tls-1-2-how-to-fix-apps/ […]
Hardcoded “ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;” is not the best approach, because we already have TLS 1.3 supported by 4.8 framework.
App should support the latest security protocol without hardcoded values.
Something like this
System.Net.ServicePointManager.SecurityProtocol=Tls | Tls11 | Tls12 | Tls13
Adding the following line directly above the ServiceClient constructor worked!:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
I can’t believe it was that simple after pulling my hair out for days. Thank you for this post. Just a note, I am on .NET 4.71, but ServiceClient was still using TLS 1 (confirmed w/Wireshark) and failing until I added the line above.