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
X509KeyStorageFlagsvalue. - Exporting the certificate file requires at least
X509KeyStorageFlags.Exportable.
- Loading the certificate file always succeeds, regardless of
- Running under IIS on a production server (not an Azure App Service) where the
w3wp.exeuser-profile is not loaded:- Loading the certificate file requires that
X509KeyStorageFlags.UserKeySetis 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."
- Loading the certificate file requires that
- Running under Azure App Service, without
WEBSITE_LOAD_CERTIFICATESdefined:- Loading the certificate with
MachineKeySetset andUserKeySetis not set fails with aCryptographicException: "Access denied." - Loading the certificate with any other
keyStorageFlagsvalue, including values likeUserKeySet | MachineKeySet | Exportableor justDefaultKeySetfails with aCryptographicException: "The system cannot find the file specified." - As I was not able to load the certificate at all I could not test exporting certificates.
- Loading the certificate with
- Running under Azure App Service, with
WEBSITE_LOAD_CERTIFICATESdefined as the thumbprint of the certificate that was uploaded:- Loading the certificate with
MachineKeySetandUserKeySetis not set, fails withCryptographicException: "Access denied." .- So values like
UserKeySetandUserKeySet | MachineKeySetandExportablewill work.
- So values like
- Exporting certificates requires
X509KeyStorageFlags.Exportable- same as all other environments.
- Loading the certificate with
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?