当创建X509Certificate2时如何防止文件创建?

12
我们在ASP.NET应用程序中创建一个X509Certificate2对象以进行定期的出站连接。每次创建这些证书时,都会在以下位置创建一个新文件:C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys。该文件夹现在有四百万个永远不会被清理的文件。我已经尝试过删除Persist标志,但这并没有帮助 - 每次调用仍然会得到2Kb的文件。当我看到这个答案的时候,我充满了希望,但这是一个2008 R2服务器,临时文件不为0字节,所以它似乎是不同的情况。我们如何在不填满磁盘的情况下使用X509Certificate2?
5个回答

7

我们的服务器也出现了同样的问题。目前我找到的最好的解决方法是从我的代码中删除文件。

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));
        }
    }
}

6

使用 .NET 4.6:

X509Certificate2 类从 .NET Framework 4.6 开始实现 IDisposable 接口;在较早版本的 .NET Framework 中,X509Certificate2 类不实现此接口,因此 Dispose 方法不存在。


1
还应注意,您也不必设置X509KeyStorageFlags.PersistKeySet,否则它将继续存在。即便如此,微软应该提供更好的方法来处理这个问题,因为在Web环境中通过某个提供程序加载证书并不总是容易在关闭期间处理掉。 - Michael Brown
@MichaelBrown:顺便说一下,当我发布这个答案时,我正在关注此问题,其中有一个有趣的讨论关于“短暂”证书-当我尝试时,它在我们的系统中出现了故障。该问题现已关闭,我没有再关注它。 - Nick Westgate
1
谢谢您对此事的跟进。我正在研究那个特定的功能,但我在 .Net Standard 中没有看到对它的支持。X509KeyStorageFlags 不包含 EphemeralKeySet,似乎他们只在 .Net Core 中添加了它,原因不明。我会继续深入挖掘。 - Michael Brown

6
为了复现您的问题,我创建了这个示例代码。我的测试环境是64位Windows 8.1,应用程序使用.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(); 
            }
        }
    }
}

我非常震惊,C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys 文件夹的文件大小达到了2MB。然后应用程序退出后,文件大小降到了20K(这可能是它的起始大小)。
接着我加入了 cert.Reset(); (如上所示的代码中我已经进行了注释)。当您不再需要 X509Certificate2 实例时,应该调用此方法。在这之后,C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys 文件大小在20K和22K之间波动。
因此,我的建议是,在您不再需要 X509Certificate2 实例时,请调用 cert.Reset(); 方法。

当我看到你的建议时,我感到很兴奋,但后来发现我已经调用了Reset :( 一个可能的区别是我将证书传递给SslStream.AuthenticateAsClient,所以我想知道是否这会添加对文件的引用...? 继续查看。 - DougN
FYI,当证书垃圾收集时,其效果与调用Reset()相同。如果您想立即释放磁盘空间,调用Reset只是加速清理的一种方式。 - Zain Rizvi
谢谢提供信息。我还没有尝试过重置方法。 - pepo
我认为垃圾回收器在这里并不适用于所有情况,如果它适用的话,问题就不会发生。奇怪的是X509Certificate2有一个Reset但没有Dispose。 - Bogdan
1
@Bogdan,X509Certificate2在.NET 4.6中有一个Dispose方法,它调用Reset。 - Nick Westgate

2

我们的服务器也遇到了同样的问题。目前我找到的最好的解决方法是通过脚本删除文件(除了有用的文件)。

例外列表 :

  • 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

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

这个解决方案只是一个补丁。最好不要在MachineKeys文件夹中生成文件。


1
我遇到了同样的问题。你是如何识别这些键的?我找不到IIS密钥。在我们的服务器上没有以c2319c42033a5ca7f44e731bfd3fa2b5开头的机器密钥文件。 - user3587767

1
我尝试使用在这里找到的建议,但在我的情况下最终失败了。 尽管文件夹开始变得更少,但仍然有一些垃圾随机留在那里。
我的情况是:我正在加载证书以在HttpClient中使用,似乎它被卡在HttpClient内部。
我已经实现了所有东西的超类(X509Certificate2,HttpClientHandler,HttpClient)来简化工作,但是一些Dispose()方法从未被调用过。
也许这与HttpClient池有关,但它似乎被设计成会失败 - 无论你做什么,都会在其他地方出现问题。
所以我决定编写一个服务,在一段时间后删除它们,最终我成功地保持了文件夹的清洁。
这是我的解决方案:
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;
        }
    }
}

你只需要使用那个类(AutoResetX509Certificate2)来加载你的证书,而不是使用X509Certificate2。
//example:
var clientCert = new AutoResetX509Certificate2(pfxBytes, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

请注意,我已经实现了我需要的两个构造函数,如果你需要另一个,你需要像这两个一样自己实现。

这仍然是一个令人沮丧的问题,九年过去了... - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接