我开发了一个使用 TLS/SSL 进行通信的 C#、.NET4.5.2 客户端/服务器系统。证书是从文件中加载的。我使用“MakeCert”实用程序创建了证书文件以创建 .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();
}
我请一些同事在他们的 PC 上运行该程序。我还尝试在虚拟机中运行,然后在我自己的笔记本电脑上设置一个新用户。在所有情况下,它在 ~0.1 秒内运行,但对于我的普通用户登录,它在 >6 秒内运行。
起初我没有在证书上调用“Reset()”,所以我认为某处的一些临时文件可能存在问题,所以我使用 procmon 来找出发生了什么。我发现在以下目录中创建了一些临时文件(尽管即使在没有调用 Reset() 的情况下,当应用程序退出时它们也会被整理):
C:\Users\<username>\AppData\Roaming\Microsoft\Crypto\RSA\<SID>
只是为了确保我已尝试删除此目录中的文件,但没有任何区别。
使用 procmon 我可以看到在加载证书期间文件/注册表活动中有 2 个间隙,这在负载快速的系统中不会发生。首先是在它尝试使用“dpapi.dll”之后。第二个是在读取以下 'C:\Extend\$UsnJrnl:$J:$DATA' 之后。DPAPI.dll 是 Windows 数据保护的接口。后一个文件是用于记录文件更改的 NTFS 的 USN 日志。我不是这方面的专家,我不确定两者是否相关!
然后我尝试使用 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
我发现很难跟踪调用堆栈,但我认为这些最终来自一个名为 CryptQueryObject 的函数。
我发现以下可能相关但不是 Windows8.1 的文章。我删除了 %windir%\Temp 文件夹以防万一,但也没有帮助。
https://support.microsoft.com/en-gb/kb/931908
我记得在某处发现一篇文章暗示延迟可能与来自 CryptQueryObject 的 ActiveDirectory 调用有关,但我找不到链接。
我真的在寻找:
- 如何修复我的用户登录,以便加载证书不需要 6 秒
- 如何确保我的代码没有问题,这样它就不会再次发生或发生在其他使用系统的人身上
谢谢你的帮助。