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 typeDirectory
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 try/catch block. To dispose of it indirectly, use a language construct such as using (in C#) .

https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2?redirectedfrom=MSDN&view=netframework-4.8
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:

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:

You may also like...

12 Responses

  1. Alexander Batishchev says:

    certificates.OfType(). FirstOrDefault () gives you nice, readable and shorter code than the one you’ve got.

  2. Learners Heaven says:

    Interesting findings. Thank you for your knowledge share.

  3. Mladen says:

    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.

  4. Mladen says:

    Does this also happen running .net core 3 on macos or linux? If so where can I find these files?

  5. gid says:

    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.

  6. Lasse says:

    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.

Leave a Reply

Your email address will not be published. Required fields are marked *