我开发了一个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调用有关但我找不到链接。
我真的在寻找:
感谢您的帮助。
答案 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更改密码。
我的问题解决了。我很欣赏这可能是相当独特的,但我希望我的调查细节在某些方面帮助别人!