Prevent file creation when X509Certificate2 is created?
Asked Answered
A

5

12

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?

Ashes answered 24/3, 2014 at 19:15 Comment(0)
T
6

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.

Triumvir answered 26/10, 2016 at 2:45 Comment(3)
It should also be noted that you also must not set X509KeyStorageFlags.PersistKeySet otherwise it will stick around. Even still, Microsoft should have provided a way better way to deal with this as it is not always easy to dispose of a certificate that has been loaded via some provider in a web environment, during shutdown.Martens
@MichaelBrown: Just FYI, at the time I posted this answer I was following this issue which has an interesting discussion about "perphemeral" certificates - which broke things in our system when I tried it. The issue is closed now and I haven't been following it.Triumvir
thanks for the follow up on that. I'm looking into that particular feature but I don't see any support for it in .Net Standard. X509KeyStorageFlags doesn't contain EphemeralKeySet and it appears they only added it to .Net Core for some reason. Will keep digging into it.Martens
A
7

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));
        }
    }
}
Alignment answered 13/4, 2018 at 20:59 Comment(0)
B
6

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.

Bindweed answered 1/4, 2014 at 7:43 Comment(5)
I was excited when I read your suggestion, but then found I already call Reset :( One difference might be that I pass the certificate to SslStream.AuthenticateAsClient, so I'm wondering if maybe that adds a reference to a file... ? Looking further.Ashes
FYI, when the cert garbage collects it has the same effect as calling Reset(). Calling reset is just a way to speed up the cleanup if you want that disk space NOWGuesswork
Thanks for the info. I didn't try with the reset method.Bindweed
I don't think the garbage collector works in all the cases in here if it would work the issue should not happen. It is strange that X509Certificate2 has a Reset but no DisposeNonintervention
@Bogdan, X509Certificate2 has a Dispose that calls Reset in .NET 4.6.Triumvir
T
6

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.

Triumvir answered 26/10, 2016 at 2:45 Comment(3)
It should also be noted that you also must not set X509KeyStorageFlags.PersistKeySet otherwise it will stick around. Even still, Microsoft should have provided a way better way to deal with this as it is not always easy to dispose of a certificate that has been loaded via some provider in a web environment, during shutdown.Martens
@MichaelBrown: Just FYI, at the time I posted this answer I was following this issue which has an interesting discussion about "perphemeral" certificates - which broke things in our system when I tried it. The issue is closed now and I haven't been following it.Triumvir
thanks for the follow up on that. I'm looking into that particular feature but I don't see any support for it in .Net Standard. X509KeyStorageFlags doesn't contain EphemeralKeySet and it appears they only added it to .Net Core for some reason. Will keep digging into it.Martens
B
2

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.

Bunyabunya answered 16/1, 2017 at 10:15 Comment(1)
I am running into same issue. how were you able to identify these keys. I couldn't find IIS key. There is no machine key file starting with c2319c42033a5ca7f44e731bfd3fa2b5 on our server.Evvie
G
1

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.

Granny answered 29/10, 2023 at 0:37 Comment(1)
Sad that this is still an issue 9 years later...Ashes

© 2022 - 2024 — McMap. All rights reserved.