The most dangerous constructor in .NET
You should never instantiate a X509Certificate2
with the “new” keyword if you can avoid it, it is one of the most dangerous constructors in .NET – X509Certificate2, and if you do, you must be aware of these gotchas. Doing this wrong can mean you flood your disk with one-time use files, that are never removed.
If you load in a new X509Certificate2 from a file by calling the public X509Certificate2 (string fileName, SecureString password);
constructor, or similar constructor then you will without knowing it, create a brand new file on your disk, and this will happen every time you new it up.
When you instantiate a X509Certificate2
from disk, say from a .pfx
file, a new storage file of 3-4kb will be created in one of the following places depending on what user account/context the code is run from, and optionally the X509KeyStorageFlags
, that you can set in one of the constructors as well.
The Microsoft legacy CryptoAPI CSPs store private keys in the following directories:
Key type | Directory |
User private | %APPDATA%\Microsoft\Crypto\RSA\User SID\ %APPDATA%\Microsoft\Crypto\DSS\User SID\ |
Local system private | %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\S-1-5-18\ %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\DSS\S-1-5-18\ |
Local Service private | %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\S-1-5-19\ %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\DSS\S-1-5-19\ |
Network Service private | %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\S-1-5-20\ %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\DSS\S-1-5-20\ |
Shared Private | %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\DSS\MachineKeys |
CNG stores private keys in the following directories.
Key type | Directory |
---|---|
User private | %APPDATA%\Microsoft\Crypto\Keys |
Local system private | %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\SystemKeys |
Local service private | %WINDIR%\ServiceProfiles\LocalService |
Network service private | %WINDIR%\ServiceProfiles\NetworkService |
Shared private | %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\Keys |
Why is this so serious?
I had a customer come to me, asking why their storage on one of their servers were constantly growing, but when they used tools like WinDirStat and similar tools for scanning files on disk, they could not see where the storage was being used.
After some analysis of their server, I found that their Local Service private
was enormous, storing ~20 million files of 4 kb, taking up 76 GB! They had built some server software that would take in SSL connections, and every time a connection was made, the .pfx
certificate would be newed up, used to handle the SSL connection, and then never disposed of.
This meant that every single request that came in, got its own certificate file in this folder. And this had happened through the last 5 years or so.
What is the right way to get a X509Certificate2 then?
Certificates are precious things, and they should be well protected. This is also why the X509Certificate2 constructor creates this new file and encrypts the content. But Windows already has a great solution for storing and retrieving certificates securely, and without wasting disk space, namely the Certificate Store.
Loading from the Certificate Store
private static X509Certificate2 FindCertificateByThumprint(string certThumbPrint) { X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine); try { // Try to open the store. store.Open(OpenFlags.ReadOnly); X509Certificate2Collection certCollection = store.Certificates; // Find currently valid certificates. X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); // Find the certificate that matches the thumbprint. X509Certificate2Collection signingCertificates = certCollection.Find( X509FindType.FindByThumbprint, certThumbPrint, false); if (signingCertificates.Count == 0) return null; return signingCertificates[0]; } finally { store.Close(); } }
Load from disk
If you really MUST load the certificate from disk, you must be absolutely certain that you use the Reset or Dispose function, to remove the file.
Pre .NET Framework 4.6
Unfortunately the Dispose functionality for a X509Certificate2 was not added before .NET 4.6, and you must therefor use the .Reset()
Starting with the .NET Framework 4.6, this type implements the IDisposable interface. When you have finished using the type, you should dispose of it either directly or indirectly. To dispose of the type directly, call its Dispose method in a
https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2?redirectedfrom=MSDN&view=netframework-4.8try
/catch
block. To dispose of it indirectly, use a language construct such asusing
(in C#) .
class Program { static void Main(string[] args) { X509Certificate2 cert = null; try { cert = new X509Certificate2("C:\dev\mycert.pfx", "mypassword"); // Use certificate here. } catch(Exception ex) { } // Handle exception please. finally { if(cert != null) { // This method can be used to reset the state of the certificate. It also frees any resources associated with the certificate. cert.Reset(); cert = null; } } } }
.NET Framework 4.6 and onwards
Luckily Microsoft came to their senses and added the IDisposable interface in .NET Framework 4.6.
class Program { static void Main(string[] args) { using(var cert = new X509Certificate2("C:\dev\mycert.pfx", "mypassword")) { // Use certificate here. } } }
It’s too late, my disk is already filled with these files
Depending on your situation, this can be quite serious. These files exist for a reason, and there are files in these folders that cannot, under any circumstances be deleted. Microsoft does not provide any way for us to automatically clean these files. And I’ve yet to find anyone who can tell me how we can look these up.
For example, the folder for Shared Private, is used to stored MachineLevel certificates, such as:
- Your own SSL Certificates
- Microsoft Internet Information Server Certificate
- NetFrameworkConfigurationKey
- iisWasKey
- WMSvc Certificate Key Container
- iisConfigurationKey
- MS IIS DCOM Server
- TSSecKeySet1
If the folder is the Local Service private you might be better off, as it stores the values for the Local Services only. Meaning this is probably one of your own Windows services.
!!!Warning!!! Deleting any of these files can completely trash your PC, so please do so at your own risk.
Before starting, make sure you have a complete backup of your system! Secondly, make sure you fix the bug creating these files, to begin with, so the folder does not keep growing.
What we did was figure out all known certificates in our certificate store, and add their Unique container name
, to an exclude list. We cross-checked with different sites to make sure we got them all we could think of. The Unique Container Name
is also the file name in the folder.
We then ran through all the files, deleted all that was unknown to our exclude list. And fired everything back up.
Thankfully it worked!
See how others fixed it:
- https://forums.iis.net/t/1224708.aspx?C+ProgramData+Microsoft+Crypto+RSA+MachineKeys+is+filling+my+disk+space
- https://stackoverflow.com/questions/34527477/clean-my-machinekeys-folder-by-removing-multiple-rsa-files-without-touching-iis?noredirect=1&lq=1
- https://stackoverflow.com/questions/22618568/prevent-file-creation-when-x509certificate2-is-created
Other ideas:
- Add some sort of listener to the files, to detect when they were last used. Over a longer period, we should be able to determine what files are actually used, and what are garbage!
Microsoft needs to fix this
In my mind, I really think Microsoft needs to address this. As a minimum as a warning in the documentation for the X509Certificate2, it is unreasonable to think that a developer should expect a new file to be created, every time he creates a new instance of this object.
Even if a file must be created, I don’t see why the same certificates creates a brand new file, every time!
Finally, we should have a way to open these files, to determine if they are needed or not. Or even better that Microsoft gives us a way to clean up these old files.
References:
- https://docs.microsoft.com/da-dk/windows/win32/seccng/key-storage-and-retrieval
- https://security.stackexchange.com/questions/1771/how-can-i-enumerate-all-the-saved-rsa-keys-in-the-microsoft-csp/102923
- https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2
- https://forums.iis.net/t/1224708.aspx?C+ProgramData+Microsoft+Crypto+RSA+MachineKeys+is+filling+my+disk+space
certificates.OfType(). FirstOrDefault () gives you nice, readable and shorter code than the one you’ve got.
Interesting findings. Thank you for your knowledge share.
You log a bug for this on github?
It’s not really a bug, just a scary side effect. I belive some redditor took my blog, and reported an issue. But to be honest I have not done more about this topic after writing the article.
Does this also happen running .net core 3 on macos or linux? If so where can I find these files?
I don’t believe so. But if you are unsure, you can use the X509KeyStorageFlags.EphemeralKeySet enum option in one of the constructors.
Sadly that option is not supported on MacOs it seems…
This option is not present in .Net Standard, it would seem only .Net Core
Update: You can simply use `((X509KeyStorageFlags)32)` to get around this in .Net Standard
How do you get the “Unique container name” of the certificate? I see that 99% of the files in this directory are close to the same name. The last 30 chars or so are all the same. The thing is that on my two servers these files are not named the same thing. I am guessing that somehow the name is unique to the machine the cert is written to, maybe? When I debug and look in my X509 I don’t see those string of chars anywhere in that object. Any help would be appreciated.
I don’t remember the exact property to look in, but if you drill down into the private key part of the object, you will find a container name.
I think there’s a mistake in the code example “Loading from the Certificate Store”, the variable “currentCerts” is never used to find the cert by thumbprint, instead “certCollection” is used.