Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

X509Certificate2 on Azure App Services (Azure Websites) since mid-2017?

I have a .NET Framework 4.7 application that allows users to upload X.509 certificates in PFX or PKCS#12 format (think: "SSL certificates" with the private key included), it then loads the certificate into a System.Security.Cryptography.X509Certificates.X509Certificate2 instance. As my application code also needs to re-export the certificate I specify the X509KeyStorageFlags.Exportable option.

When running under IIS on my production web-server, the Windows user-profile for the identity that w3wp.exe runs under is not loaded, so I do not specify the UserKeySet flag.

String filePassword = ...
Byte[] userProvidedCertificateFile = ...

using( X509Certificate2 cert = new X509Certificate2( rawData: userProvidedCertificateFile, password: filePassword, keyStorageFlags: X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet )
{
    ...
}

In early 2017 I deployed this code to an Azure App Service (aka Azure Website) instance and it worked okay - after initially failing because I did have the UserKeySet flag set (as Azure App Services do not load a user-profile certificate store.

However, since mid-2017 (possibly around May or June) my application has stopped working - I assume the Azure App Service was moved to an updated system (though Kudu reports my application is running on Windows Server 2012 (NT 6.2.9200.0).

It currently fails with two error messages that varied depending on input:

  • CryptographicException "The system cannot find the file specified."
  • CryptographicException "Access denied."

I wrote an extensive test-case that tries different combinations of X509Certificate2 constructor arguments, as well as with and without the WEBSITE_LOAD_CERTIFICATES Azure application setting.

Here are my findings when working with an uploaded PFX/PKCS#12 certificate file that contains a private key and does not have password-protection:

  • Running under IIS Express on my development box:
    • Loading the certificate file always succeeds, regardless of X509KeyStorageFlags value.
    • Exporting the certificate file requires at least X509KeyStorageFlags.Exportable.
  • Running under IIS on a production server (not an Azure App Service) where the w3wp.exe user-profile is not loaded:
    • Loading the certificate file requires that X509KeyStorageFlags.UserKeySet is not set, but otherwise always succeeds.
    • Exporting the certificate file requires at least X509KeyStorageFlags.Exportable, but otherwise always succeeds, otherwise it fails with "Key not valid for use in specified state."
  • Running under Azure App Service, without WEBSITE_LOAD_CERTIFICATES defined:
    • Loading the certificate with MachineKeySet set and UserKeySet is not set fails with a CryptographicException: "Access denied."
    • Loading the certificate with any other keyStorageFlags value, including values like UserKeySet | MachineKeySet | Exportable or just DefaultKeySet fails with a CryptographicException: "The system cannot find the file specified."
    • As I was not able to load the certificate at all I could not test exporting certificates.
  • Running under Azure App Service, with WEBSITE_LOAD_CERTIFICATES defined as the thumbprint of the certificate that was uploaded:
    • Loading the certificate with MachineKeySet and UserKeySet is not set, fails with CryptographicException: "Access denied." .
      • So values like UserKeySet and UserKeySet | MachineKeySet and Exportable will work.
    • Exporting certificates requires X509KeyStorageFlags.Exportable - same as all other environments.

So it seems that WEBSITE_LOAD_CERTIFICATES seems to work - but only if the certificate being loaded into an X509Certificate2 instance has the same thumbprint as specified in WEBSITE_LOAD_CERTIFICATES.

Is there any way around this?

like image 659
Dai Avatar asked Sep 08 '17 10:09

Dai


1 Answers

I thought more about how WEBSITE_LOAD_CERTIFICATES seems to make a difference - but I had a funny feeling about it really only working with the certificate thumbprint that's specified.

So I changed the WEBSITE_LOAD_CERTIFICATES value to a dummy thumbprint - an arbitrary 40-character Base16 string, and re-ran my test - and it worked, even though the thumbprint had no relation to the certificate I was working with.

It seems that simply having WEBSITE_LOAD_CERTIFICATES defined will enable the the Azure website's ability to use X509Certificate and X509Certificate2 - even if the loaded certificate is never installed into, or even retrieved from, any systemwide or user-profile certificate store (as seen in the Certificates snap-in for MMC.exe).

This behaviour does not seem to be documented anywhere, so I'm mentioning it here.

I've contacted Azure support about this.

Regarding the behavioural change I noticed at mid-year - it's very likely that I did have WEBSITE_LOAD_CERTIFICATES originally set for a testing certificate we were using. When I made a new deployment later in the year around June I must have reset the Application settings which removed the WEBSITE_LOAD_CERTIFICATES and so broke X509Certificate2 instances.

TL;DR:

  1. Open your Azure App Service (Azure Website) blade in portal.azure.com
  2. Go to the Application settings page
  3. Scroll to App settings
  4. Add a new entry key: WEBSITE_LOAD_CERTIFICATES, and provide a dummy (fake, made-up, randomly-generated) value for it.
  5. The X509Certificate2( Byte[], String, X509KeyStorageFlags ) constructor will now work, but note:
    • keyStorageFlags: X509KeyStorageFlags.MachineKeySet will fail with "Access denied"
    • All other keyStorageFlags values, including MachineKeySet | UserKeySet will succeed (i.e. MachineKeySet by itself will fail, but MachineKeySet used in conjunction with other bits set will work).
like image 89
Dai Avatar answered Nov 15 '22 15:11

Dai