That's a lot of damage - in Azure!

How to force an outdated .NET project to use TLS 1.2?

4 min read.

This article explains a kind of weird fix to a pretty annoying issue with a legacy service. And I’m not just calling it legacy because it’s WCF – I’m calling it legacy, because it was developed about 7 years ago (2014-ish)! It had worked for quite a while – but now it was down.

So – I was brought onboard to solve the issue. The service was supposed to be connecting to SharePoint Online. It was originally developed by a 3rd party who were not involved anymore, and even if they were they might not have been useful, as the lead developer had passed away anyway.

Problem

The service was developed in .NET Framework 4.5.1. The developer was not available. The code was not available. All of the exceptions were seemingly swallowed. Application Insights wasn’t in use.

Okay, I just… Yeah. Where do you start with this?

Well, I started by enabling Application Insights and reproducing the issue.

And these failed dependencies looked somewhat like this:

Okay. Simple enough. Connection from Azure App Service to SharePoint failed.

The next step was downloading the binaries, investigating them with dotPeek and copy-pasting the a super simplified version of the reflected code to a new console application, developed in .NET Framework 4.5.1.

And sure enough, it didn’t work. I got the error below:

System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. 
         ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. 
         ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host\r\n   
         at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)\r\n   
         at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)\r\n   
         --- End of inner exception stack trace ---\r\n   
         at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)\r\n   
         at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)\r\n   
         at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)\r\n   
         at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)\r\n   
         at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)\r\n   
         at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)\r\n   
         at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)\r\n   
         at System.Net.TlsStream.CallProcessAuthentication(Object state)\r\n   
         at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)\r\n   
         at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)\r\n   
         at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)\r\n   
         at System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result)\r\n   
         at System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size)\r\n   
         at System.Net.PooledStream.Write(Byte[] buffer, Int32 offset, Int32 size)\r\n   
         at System.Net.ConnectStream.WriteHeaders(Boolean async)\r\n   
         --- End of inner exception stack trace ---\r\n   
         at System.Net.HttpWebRequest.GetResponse()\r\n   
         at Microsoft.SharePoint.Client.Idcrl.SharePointOnlineAuthenticationProvider.GetIdcrlHeader(Uri url, Boolean alwaysThrowOnFailure, EventHandler1 executingWebRequest)\r\n            at Microsoft.SharePoint.Client.Idcrl.SharePointOnlineAuthenticationProvider.GetAuthenticationCookie(Uri url, String username, SecureString password, Boolean alwaysThrowOnFailure, EventHandler1 executingWebRequest)\r\n   
         at Microsoft.SharePoint.Client.SharePointOnlineCredentials.GetAuthenticationCookie(Uri url, Boolean refresh, Boolean alwaysThrowOnFailure)\r\n   
         at Microsoft.SharePoint.Client.ClientRuntimeContext.SetupRequestCredential(ClientRuntimeContext context, HttpWebRequest request)\r\n   
         at Microsoft.SharePoint.Client.SPWebRequestExecutor.GetRequestStream()\r\n   
         at Microsoft.SharePoint.Client.ClientContext.GetFormDigestInfoPrivate()\r\n   
         at Microsoft.SharePoint.Client.ClientContext.EnsureFormDigest()\r\n   
         at Microsoft.SharePoint.Client.ClientContext.ExecuteQuery()\r\n

What gives?

Reason

Starting late 2020 (15.10.), Microsoft started completely disallowing TLS 1.0 and 1.1 for clients connecting to SharePoint Online. See References for more details. This process was partially postponed due to COVID-19, but in March/April most of the last API endpoints stopped accepting connections using these protocols.

.NET Framework 4.5.1 uses TLS 1.0 by default, even if the platform (such as an Azure App Service) supports newer protocol versions.

So without some wizardry, your .NET Framework 4.5.1 application won’t be able to connect to SharePoint Online (among other things).

But your app already knows TLS 1.2 if it’s built on .NET Framework 4.5+. It just doesn’t use it. So out comes the wizardry.

Solution

Before you even ask: Microsoft won’t enable TLS 1.0 for you. So that’s not a solution. Instead, you’ve got a couple of options:

  1. Modernize your application to .NET Framework 4.7.2 or newer (it’ll use TLS 1.2 by default then)
  2. Add this line to your application: ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
  3. Convince your app to use TLS 1.2 using unholy and poorly documented workarounds

I didn’t have the source code available, so I couldn’t modernize the application. I couldn’t add the code for the second option either, so I was left with option 3.

But how do you convince an app it should just magically become secure all of a sudden? It’s so used to its olden ways!

Well, as always, there are workarounds available :)

Solution 1: Sweet-talk your console app to behave well

For console apps, you can fix all kinds of things using the associated .config file. And especially for this matter, you have a plethora of switches that you can apply by supplying them as a ;-separated string of key-boolean -pairs, inside a tag caled “AppContextSwitchOverrides”.

Kind of convoluted, right? And like I said, the switches are plentiful. See the whole list in the references -section.

Anyway, for this use case, Microsoft realized this beautifully named switch: DontEnableSchUseStrongCrypto.

<runtime>
	<AppContextSwitchOverrides value="Switch.System.Net.DontEnableSchUseStrongCrypto=false" />
</runtime>

Don’t enable <something> … And now we’ll set it to “false”.

So essentially, “disable something” is set to “false”. So we actually want to enable, that something, instead of not NOT enabling it.

Confusing, right? Naming stuff is hard. But this way, we’ll actually kindly suggest our application uses the strongest possible cryptography available – and for .NET Framework 4.5+, TLS 1.2 is usable, just not used by default.

And incredibly – if your first option doesn’t work, there’s another one you can use!

<runtime>
      <AppContextSwitchOverrides value="Switch.System.Net.DontEnableSystemDefaultTlsVersions=false"/>
</runtime>

This will definitely also work on some non-console apps – but our little WCF Service would not have any of our sweet talking, so we had to try something else!

Solution 2: Trick your web-app to think it knows TLS 1.2

When sweet talking doesn’t help, it’s time for some trickery!

Time needed: 10 minutes.

How to cheat your web app into using TLS 1.2?

  1. Find your web.config file

    If you’re editing a live application in Azure App Service, access Kudu and navigate to site -> wwwroot, and the web.config is either in the root or in the folder mapped to your virtual path.

  2. Change your Target Framework to be 4.7.2 or newer

    Inside <configuration>, you should find <system.web> (and if you don’t, just create it). Add something like this inside it:
    This image has an empty alt attribute; its file name is image-1.png

  3. TLS 1.2 for fun and profit!

    Your application now things it should use the defaults for .NET Framework 4.7.2, and, like magic, it switches to TLS 1.2.

Hey, if it’s stupid and it works, it’s not stupid.

Oh – and here’s a copy-pasteable web.config file for your reference:

<configuration>
 <system.web>
  <compilation targetFramework="4.5.1" />
  <httpRuntime targetFramework="4.7.2"/>
 </system.web>
</configuration>

References

Some of these links helped, some didn’t really, but I’m documenting them here anyway for any later usage.

mm
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments