Reputation: 4607
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
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
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
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 :
This solution remains a patch. It would be better to no generate files in MachineKeys folder.
Upvotes: 2
Reputation: 3273
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
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