DougN
DougN

Reputation: 4607

Prevent file creation when X509Certificate2 is created?

We create a X509Certificate2 object in our ASP.NET app to make periodic outgoing connections. Every time one of these certificates is created a new file is created in:

C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys

That folder now has 4 million files that never get cleaned up. I've tried removing the Persist flag

new X509Certificate2(certBytes, p12Pwd, X509KeyStorageFlags.MachineKeySet);

//no X509KeyStorageFlags.PersistKeySet

but that doesn't help -- still get the 2Kb file on every call.

I got my hopes up when I saw this answer, but this is a 2008 R2 server, and the temp files are not 0 bytes, so it seems to be a different case.

How can we use a X509Certificate2 without filling up the disk?

Upvotes: 12

Views: 8536

Answers (5)

Cesar
Cesar

Reputation: 595

I tried to use the suggestions I found here, but in my case it eventually fails. Although the folder started to grow less, yet some junk are randomly left in there.

My case: I'm loading the certificate to use in the HttpClient, and seems it gets stuck inside the HttpClient.

I've implemented superclasses of everything (X509Certificate2, HttpClientHandler, HttpClient) to ease the job, but somehow the Dispose() methods are not called. Never.

Maybe it is related to HttpClient pool, but it seems it was designed to fail - no matter what you do, something will fail in another place.

So I decided to write a service which deletes them after a time, finally I managed to keep the folder clean.

Here is:

public class AutoResetX509Certificate2 : X509Certificate2
{
    string autoGeneratedFile = "";
    const string keyFolder = @"C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys";

    public AutoResetX509Certificate2(byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags) : base(rawData, password, keyStorageFlags)
    {
        var uniqueName = ((System.Security.Cryptography.RSACng)this.PrivateKey).Key.UniqueName;
        autoGeneratedFile = Path.Combine(keyFolder, uniqueName);
        AddFileToDeletion(autoGeneratedFile);
        StartAutoResetService();
    }

    public AutoResetX509Certificate2(byte[] rawData) : base(rawData)
    {
        var uniqueName = ((System.Security.Cryptography.RSACng)this.PrivateKey).Key.UniqueName;
        autoGeneratedFile = Path.Combine(keyFolder, uniqueName);
        AddFileToDeletion(autoGeneratedFile);
        StartAutoResetService();
    }

    protected override void Dispose(bool disposing)
    {
        StopAutoResetService();
        base.Dispose(disposing);
    }

    private void AddFileToDeletion(string path)
    {
        var folder = Path.Combine(keyFolder, "!deletionTrack");
        Directory.CreateDirectory(folder);
        var fi = new FileInfo(path);
        var file = Path.Combine(folder, fi.Name);
        File.WriteAllText(file, "");
    }

    //======================= AutoResetService

    static System.Timers.Timer timer = new() { Enabled = false, Interval = 5000 };

    static bool isTimerBusy = false;

    internal static void StartAutoResetService()
    {
        lock (timer)
        {
            if (!timer.Enabled)
            {
                timer.Elapsed += Timer_Elapsed;
                timer.Enabled = true;
            }
        }
    }

    internal static void StopAutoResetService()
    {
        lock (timer)
        {
            timer.Enabled = false;
            timer.Elapsed -= Timer_Elapsed;
        }
    }

    private static void RunAutoReset()
    {
        var folder = Path.Combine(keyFolder, "!deletionTrack");
        var di = new DirectoryInfo(folder);
        if (di.Exists)
        {
            var olders = di.GetFiles().Where(fi => DateTime.Now.Subtract(fi.CreationTime).TotalSeconds > 30).ToList();
            foreach (var old in olders)
            {
                var certPath = Path.Combine(keyFolder, old.Name);
                if (File.Exists(certPath)) File.Delete(certPath);
                old.Delete();
            }
        }
    }

    private static void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (isTimerBusy) return;
        isTimerBusy = true;
        try
        {
            RunAutoReset();
        }
        catch(Exception)
        {
            //implement your logger here if you need it.
        }
        finally
        {
            isTimerBusy = false;
        }
    }
}

You just need to load your certificate using that class (AutoResetX509Certificate2) instead of X509Certificate2.

//example:
var clientCert = new AutoResetX509Certificate2(pfxBytes, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

Note I implemented the two constructors I needed, if you need another you have a to implement like these two.

Upvotes: 1

Dmitry Kuznetsov
Dmitry Kuznetsov

Reputation: 71

I have the same issue on our server. The best way I found for now is to delete files from my code.

using System;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Byte[] bytes = File.ReadAllBytes(@"D:\tmp\111111111111.p12");
            X509Certificate2 x509 = new X509Certificate2(bytes, "qwerty", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var privateKey = x509.PrivateKey as RSACryptoServiceProvider;
            string uniqueKeyContainerName = privateKey.CspKeyContainerInfo.UniqueKeyContainerName;
            x509.Reset();

            File.Delete(string.Format(@"C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\{0}", uniqueKeyContainerName));
        }
    }
}

Upvotes: 7

BaptX
BaptX

Reputation: 589

I have the same issue on our server. The best way I found for now is to delete files (except useful ones) via a script.

Exception list :

  • Microsoft Internet Information Server -> c2319c42033a5ca7f44e731bfd3fa2b5 ...
  • NetFrameworkConfigurationKey -> d6d986f09a1ee04e24c949879fdb506c ...
  • iisWasKey -> 76944fb33636aeddb9590521c2e8815a ...
  • WMSvc Certificate Key Container -> bedbf0b4da5f8061b6444baedf4c00b1 ...
  • iisConfigurationKey -> 6de9cb26d2b98c01ec4e9e8b34824aa2 ...
  • MS IIS DCOM Server -> 7a436fe806e483969f48a894af2fe9a1 ...
  • TSSecKeySet1 -> f686aace6942fb7f7ceb231212eef4a4 ...
  • https://forums.whirlpool.net.au/archive/1683713

via https://forums.iis.net/t/1224708.aspx?C+ProgramData+Microsoft+Crypto+RSA+MachineKeys+is+filling+my+disk+space

This solution remains a patch. It would be better to no generate files in MachineKeys folder.

Upvotes: 2

Nick Westgate
Nick Westgate

Reputation: 3273

Use .NET 4.6:

X509Certificate2 implements the IDisposable interface starting with the .NET Framework 4.6; in previous versions of the .NET Framework, the X509Certificate2 class does not implement this interface, and therefore the Dispose method does not exist.

Upvotes: 6

pepo
pepo

Reputation: 8867

To reproduce your problem I have created this sample code. My testing environment was Windows 8.1 64bit and the application was written in .NET 4.5.

using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var certBytes = File.ReadAllBytes(@"c:\cert.p12");
            var p12Pwd = "somepassword";

            for (var i = 0; i < 1000; i++)
            {
                var cert = new X509Certificate2(certBytes, p12Pwd, X509KeyStorageFlags.MachineKeySet);

                // this line helped keep filesize from growing   
                // cert.Reset(); 
            }
        }
    }
}

I was shocked that file size of C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys went up to 2MB. Then application exited and the filesize dropped down to 20K (that was probably its starting size).

Then I have added cert.Reset(); (I have commented it in the code above). This should be called when you no longer need the X509Certificate2 instance. After that, the filesize of C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys was flapping between 20K and 22K.

So my suggestion is to call cert.Reset(); when you no longer need the X509Certificate2 instance.

Upvotes: 6

Related Questions