Configure ServicePointManager.SecurityProtocol through AppSettings
Niels Swimberghe - - .NET
Follow me on Twitter, buy me a coffee
As time passes, webservers move to newer security protocols and remove deprecated security protocols. This may lead to errors in your application when you make HTTP requests. Usually .NET automatically finds a security protocol in common, but sometimes you have to update ServicePointManager.SecurityProtocol
explicitly.
You can remove security protocols using a binary AND operator and a Bitwise complement to inverse the bits:
// removes SSL3 ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3; // remove multiple protocols, SSL3 and TLS 1.0 ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls);
You can add security protocols using a Binary OR operator:
// add TLS 1.1 ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11; // add multiple protocols, TLS 1.1 and TLS 1.2 ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
Instead of hardcoding the additions/removals into your codebase, you could also use config-files with appSettings. Here's an example of what your configuration file would look like:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> <appSettings> <add key="SecurityProtocols.Remove" value="Ssl3,Tls" /> <add key="SecurityProtocols.Add" value="Tls11,Tls12" /> </appSettings> </configuration>
In your application, run the following methods RemoveSecurityProtocols
then AddSecurityProtocols
:
private static void RemoveSecurityProtocols() { var securityProtocolsToRemove = ConfigurationManager.AppSettings["SecurityProtocols.Remove"] ?.Split(',') .Select(o => o.Trim()) .ToArray(); if (securityProtocolsToRemove == null || securityProtocolsToRemove.Length == 0) { return; } foreach (var securityProtocolString in securityProtocolsToRemove) { SecurityProtocolType securityProtocolEnum; if (Enum.TryParse(securityProtocolString, out securityProtocolEnum)) { // removes security protocol using binary operation ServicePointManager.SecurityProtocol &= ~securityProtocolEnum; } } } private static void AddSecurityProtocols() { var securityProtocolsAdd = ConfigurationManager.AppSettings["SecurityProtocols.Add"] ?.Split(',') .Select(o => o.Trim()) .ToArray(); if (securityProtocolsAdd == null || securityProtocolsAdd.Length == 0) { return; } foreach (var securityProtocolString in securityProtocolsAdd) { SecurityProtocolType securityProtocolEnum; if (Enum.TryParse(securityProtocolString, out securityProtocolEnum)) { // adds security protocol using binary operation ServicePointManager.SecurityProtocol |= securityProtocolEnum; } } }
Here's a sample .NET Framework console application that sets the available security protocols to SSL3 only. This will intentionally fail to match security protocols when requesting 'swimburger.net':
var originalSecurityProtocol = ServicePointManager.SecurityProtocol; try { Console.WriteLine("Request swimburger.net using SSL3, this should fail. Press any key to start request."); Console.ReadKey(); ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; await httpClient.GetAsync("https://swimburger.net"); } catch (HttpRequestException ex) { Console.WriteLine(ex.ToString()); }
The console output will look like this:
Request swimburger.net using SSL3, this should fail. Press any key to start request. System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a receive. ---> System.ComponentModel.Win32Exception: The client and server cannot communicate, because they do not possess a common algorithm at System.Net.SSPIWrapper.AcquireCredentialsHandle(SSPIInterface SecModule, String package, CredentialUse intent, SecureCredential scc) at System.Net.Security.SecureChannel.AcquireCredentialsHandle(CredentialUse credUsage, SecureCredential& secureCredential) at System.Net.Security.SecureChannel.AcquireClientCredentials(Byte[]& thumbPrint) at System.Net.Security.SecureChannel.GenerateToken(Byte[] input, Int32 offset, Int32 count, Byte[]& output) at System.Net.Security.SecureChannel.NextMessage(Byte[] incoming, Int32 offset, Int32 count) at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult) at System.Net.TlsStream.CallProcessAuthentication(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result) at System.Net.TlsStream.BeginWrite(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback asyncCallback, Object asyncState) at System.Net.TlsStream.UnsafeBeginWrite(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback asyncCallback, Object asyncState) at System.Net.PooledStream.UnsafeBeginWrite(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state) at System.Net.ConnectStream.WriteHeaders(Boolean async) --- End of inner exception stack trace --- at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult) at System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar) --- End of inner exception stack trace --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at ChangeSecurityProtocols.Program.<Main>d__1.MoveNext() in C:\Users\niels\source\repos\ChangeSecurityProtocols\Program.cs:line 22
As you can see by the output, an HttpRequestException
is thrown and the inner inner exception is "System.ComponentModel.Win32Exception: The client and server cannot communicate, because they do not possess a common algorithm".
When you add TLS1.1 & 1.2 back by running RemoveSecurityProtocols
then AddSecurityProtocols
, the HTTP request succeeds:
RemoveSecurityProtocols(); AddSecurityProtocols(); Console.WriteLine("Request swimburger.net with Tls11,Tls12 enabled, this should succeed. Press any key to start request."); Console.ReadKey(); var response = await httpClient.GetAsync("https://swimburger.net"); Console.WriteLine($"Response: {response.StatusCode}");
The output looks like this:
Request swimburger.net with Tls11,Tls12 enabled, this should succeed. Press any key to start request. Response: OK
Here's the entire console sample:
using System; using System.Configuration; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace ChangeSecurityProtocols { class Program { private static HttpClient httpClient = new HttpClient(); static async Task Main(string[] args) { var originalSecurityProtocol = ServicePointManager.SecurityProtocol; try { Console.WriteLine("Request swimburger.net using SSL3, this should fail. Press any key to start request."); Console.ReadKey(); ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; await httpClient.GetAsync("https://swimburger.net"); } catch (HttpRequestException ex) { Console.WriteLine(ex.ToString()); } // update security protocols from appSettings RemoveSecurityProtocols(); AddSecurityProtocols(); Console.WriteLine("Request swimburger.net with Tls11,Tls12 enabled, this should succeed. Press any key to start request."); Console.ReadKey(); var response = await httpClient.GetAsync("https://swimburger.net"); Console.WriteLine($"Response: {response.StatusCode}"); Console.WriteLine("Press any key to exit."); Console.ReadKey(); } private static void RemoveSecurityProtocols() { var securityProtocolsToRemove = ConfigurationManager.AppSettings["SecurityProtocols.Remove"] ?.Split(',') .Select(o => o.Trim()) .ToArray(); if (securityProtocolsToRemove == null || securityProtocolsToRemove.Length == 0) { return; } foreach (var securityProtocolString in securityProtocolsToRemove) { SecurityProtocolType securityProtocolEnum; if (Enum.TryParse(securityProtocolString, out securityProtocolEnum)) { // removes security protocol using binary operation ServicePointManager.SecurityProtocol &= ~securityProtocolEnum; } } } private static void AddSecurityProtocols() { var securityProtocolsAdd = ConfigurationManager.AppSettings["SecurityProtocols.Add"] ?.Split(',') .Select(o => o.Trim()) .ToArray(); if (securityProtocolsAdd == null || securityProtocolsAdd.Length == 0) { return; } foreach (var securityProtocolString in securityProtocolsAdd) { SecurityProtocolType securityProtocolEnum; if (Enum.TryParse(securityProtocolString, out securityProtocolEnum)) { // adds security protocol using binary operation ServicePointManager.SecurityProtocol |= securityProtocolEnum; } } } } }