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:
X509KeyStorageFlags value.X509KeyStorageFlags.Exportable.w3wp.exe user-profile is not loaded:
X509KeyStorageFlags.UserKeySet is not set, but otherwise always succeeds.X509KeyStorageFlags.Exportable, but otherwise always succeeds, otherwise it fails with "Key not valid for use in specified state."WEBSITE_LOAD_CERTIFICATES defined:
MachineKeySet set and UserKeySet is not set fails with a CryptographicException: "Access denied."keyStorageFlags value, including values like UserKeySet | MachineKeySet | Exportable or just DefaultKeySet fails with a CryptographicException: "The system cannot find the file specified."WEBSITE_LOAD_CERTIFICATES defined as the thumbprint of the certificate that was uploaded:
MachineKeySet and UserKeySet is not set, fails with CryptographicException: "Access denied." .
UserKeySet and UserKeySet | MachineKeySet and Exportable will work.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?
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.
portal.azure.com
WEBSITE_LOAD_CERTIFICATES, and provide a dummy (fake, made-up, randomly-generated) value for it.X509Certificate2( Byte[], String, X509KeyStorageFlags ) constructor will now work, but note:
keyStorageFlags: X509KeyStorageFlags.MachineKeySet will fail with "Access denied"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).If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With