X509Certificate构造函数需要> 6秒才能为特定用户

时间:2016-07-19 19:31:17

标签: c# ssl cryptoapi x509certificate2 dpapi

我开发了一个C#,.NET4.5.2客户端/服务器系统,它使用TLS / SSL进行通信。证书是从文件加载的。我使用'MakeCert'utitilty创建了证书文件来创建.pvk和.cer文件然后我使用'pvk2pfx'实用程序将它们组合成.pfx。

要使用证书,我使用带有重载的X509Certificate2构造函数加载它们,将文件路径和密码作为字符串传递:

X509Certificate2(string filePath, string password)

随着时间的推移,我注意到证书的加载变得非常缓慢。我不确定是否有一个'事件'使它们变慢或者它是渐进的但是现在加载PFX文件大约需要6秒钟。加载CER文件没问题,需要大约0.1秒。

我正在运行Windows 8.1,问题仅出在笔记本电脑上的用户登录。

我编写了以下测试应用来验证问题:

    private const string filePath = @"c:\testcert.pfx";
    private const string password = "testpassword";
    static void Main(string[] args)
    {
        var stopwatch = new Stopwatch();
        try
        {
            Console.WriteLine("About to create certificate. Press Enter.");
            Console.ReadLine();

            stopwatch.Start();
            var cert = new X509Certificate(filePath, password);
            stopwatch.Stop();

            Console.WriteLine(stopwatch.Elapsed);
            Console.WriteLine("Certificate created. About to reset.  Press Enter.");
            Console.ReadLine();

            cert.Reset();

            Console.WriteLine("Certificate reset.  Press Enter.");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Console.ReadLine();
    }

我让一些同事在他们的电脑上运行该程序。我也尝试在VM中运行,然后在自己的笔记本电脑上设置新用户。在所有情况下,它运行约0.1秒,但对于我的普通用户登录,它运行时间> 6秒。

起初我没有在证书上调用“重置()”,所以想到某些临时文件可能存在问题,所以我用procmon来查明发生了什么。我发现在以下目录中创建了一些临时文件(尽管即使没有调用Reset(),当应用程序退出时它们也会被整理掉):

C:\Users\<username>\AppData\Roaming\Microsoft\Crypto\RSA\<SID>

为了确保我已经尝试删除此目录中的文件,但它没有任何区别。

使用procmon我可以看到在加载证书期间文件/注册表活动中存在2个空白,这在加载速度快的系统中不会发生。首先是它尝试使用'dpapi.dll'之后。第二个是在读取以下'C:\ Extend \ $ UsnJrnl:$ J:$ DATA'之后。 DPAPI.dll是Windows Data Protection的界面。后一个文件是USN Journal for NTFS,它记录了文件更改。我也不是专家,我不确定两者是否相关!

然后我尝试使用API​​ Monitor http://www.rohitab.com/apimonitor来观察系统调用。我再也不是专家了,但是我一直在寻找在停顿之前发生的事情。那里有很多我不明白的可能或可能没有相关性,我欢迎任何评论,以帮助集中精力解决这个问题。

在2秒间隙之前的最后一次调用是具有以下调用堆栈的memcpy:

#   Module  Address Offset  Location
1   RPCRT4.dll  0x74f6378b  0x2378b I_RpcSendReceive + 0x1bb
2   RPCRT4.dll  0x74f6367b  0x2367b I_RpcSendReceive + 0xab
3   RPCRT4.dll  0x74f594df  0x194df NdrServerInitializeNew + 0x83f
4   RPCRT4.dll  0x74f63619  0x23619 I_RpcSendReceive + 0x49
5   RPCRT4.dll  0x74f6398b  0x2398b NdrSendReceive + 0x2b

更高的可能有趣的线似乎是:

#   Time of Day Thread  Module  API Return Value    Error   Duration
64922   6:38:44.348 AM  1   DPAPI.dll   SystemFunction040 ( 0x00ac5a30, 8, RTL_ENCRYPT_OPTION_SAME_LOGON )  STATUS_SUCCESS      0.0000402
64923   6:38:44.349 AM  1   CRYPTBASE.dll   RtlInitUnicodeString ( 0x0090e5a8, "\Device\KsecDD" )           0.0000004

64949   6:38:44.349 AM  1   RPCRT4.dll  RtlInitUnicodeString ( 0x0090e0b0, "\RPC Control\protected_storage" )           0.0000000

我发现很难跟踪callstack,但我认为这些最终来自一个名为CryptQueryObject的函数。

我发现以下文章可能是相关的,但它不是Windows8.1。我已经删除了%windir%\ Temp文件夹以防万一,但也没有帮助。

https://support.microsoft.com/en-gb/kb/931908

我记得在某个地方找到一篇文章,暗示延迟可能与来自CryptQueryObject的ActiveDirectory调用有关但我找不到链接。

我真的在寻找:

  1. 如何修复我的用户登录,因此加载证书不需要6秒
  2. 如何确保我的代码正常,以免再次发生或使用系统的其他人
  3. 感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

此问题现已解决,我认为我理解为什么!

当您使用私钥加载X509Certificate时,Windows会将密钥存储到文件中,并使用“数据保护”对文件进行加密(因此,我监控的DPAPI调用)。

https://technet.microsoft.com/en-us/library/cc962112.aspx

用于加密文件的密钥称为“主密钥”。它基于您的用户登录,并在您更改使用密码时续订。它也会在90天后自动失效。

http://www.passcape.com/index.php?section=docsys&cmd=details&id=28#33

如上面的链接所述,主密钥存储在以下目录中:

%APPDATA%/Microsoft/Protect/%SID%

通过查看该目录中文件的“最后修改日期”,我可以看到我的MasterKey实际上超过了90天。因此,每当我加载证书时,它都试图将私钥存储在加密文件中,并注意到主密钥已过期,因此它会尝试更新它。

我重新尝试ProcMon,但仅停止从我的测试应用程序中筛选活动。然后,我可以看到在“间隙”期间,通过名为lsass.exe的进程在我们的Active Directory服务器上进行了多次UDP调用,这与安全性和密码更新有关(由于堆栈溢出,无法发布另一个链接)限制,但很容易搜索)。

我认为尝试更新主密钥的过程必须涉及与ActiveDirectory服务的一些交互。

我远程工作并通过VPN连接到我的工作网络。 VPN不允许访问ActiveDirectory服务器,所以我认为延迟是由尝试和无法访问该服务器引起的。

我等到下次访问办公室时,一旦我连接到网络并可以访问ActiveDirectory服务器,那么加载证书的延迟就会消失。我确认我的MasterKey文件也已更新,不再过时。

我在通过VPN连接时重新测试了加载证书,并且延迟仍然消失,所以只要主密钥没有过时,这似乎确认它是正常的。

不幸的是,由于无法访问Active Directory服务的相同问题,我无法通过VPN更改密码。

我的问题解决了。我很欣赏这可能是相当独特的,但我希望我的调查细节在某些方面帮助别人!