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.