首先一些背景
致电后SslStream.AuthenticateAsClient() https://msdn.microsoft.com/en-us/library/ms145061(v=vs.110).aspx要启动 TLS/SSL 握手,可以向用户显示以下“Windows 安全”对话框:
Windows security: This application needs to use a cryptographic key
当满足以下两个原因时就会发生这种情况:
- 客户端尝试连接的 SSL 服务器请求客户证书作为 TLS/SSL 握手的一部分。
- The X509证书 https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate(v=vs.110).aspx通过第二个参数传递SslStream.AuthenticateAsClient() https://msdn.microsoft.com/en-us/library/ms145060(v=vs.110).aspx或通过本地证书选择回调 https://msdn.microsoft.com/en-us/library/system.net.security.localcertificateselectioncallback(v=vs.110).aspx回调(当构建SslStream https://msdn.microsoft.com/en-us/library/ms145057(v=vs.110).aspx) was 严格保护 https://blogs.technet.microsoft.com/pki/2009/06/16/what-is-a-strong-key-protection-in-windows/。 (对话框本身的外观会根据所谓的调用而有所不同安全级别的保护。)
在更高版本的 Windows(Win8 和 Win10)上,此对话框由名为“的外部程序显示”凭据UIBroker.exe“这是由svchost.exe。在早期版本中,它是由加载到正在运行的程序本身中的 dll 呈现的:comctl32.dll在Win7和cryptui.dll在WinXP中
问题
虽然此 Windows 安全对话框看起来是一个模式对话框,但它的行为更像是一个模式对话框没有所有者参数设置.
这会导致以下问题:
- 该对话框可以(而且经常)在正在运行的程序的窗口后面打开,从而使用户很难发现它。
- 用户单击正在运行的程序的其他窗口时,该对话框可能会隐藏在后台,从而导致混乱。
- 当对话框打开时,正在运行的程序的其他窗口上的 UI 元素不会冻结,并且用户可以自由执行其他操作。
所以问题是:如何进行设置以使 Windows 安全对话框显示为模式对话框?
其他软件中出现的问题
Chrome 遇到了这个问题,并且迄今为止尚未修复(Chrome 51)(错误跟踪:https://bugs.chromium.org/p/chromium/issues/detail?id=304152 https://bugs.chromium.org/p/chromium/issues/detail?id=304152)
Internet Explorer 不会遇到此问题。它将 Windows 安全对话框显示为模式对话框。
Firefox 不适用,因为它从未使用过 Windows 的证书存储,而是依赖于自己的存储。
要重现的代码
显示 Windows 安全 UI 有点复杂。
首先,它需要使用导入的证书强有力的保护导入 UI 期间检查的选项。 (旁注:使用的任何证书也应该是不可导出的,因为仅适用于可导出证书的解决方案不适合生产。)
下面的代码还需要服务器证书(任何证书without强有力的保护就可以了),因为我们正在使用SslStream.AuthenticateAsClientAsync()
在虚假的 TLS/SSL 连接中。
而且,FullDuplexPipeStream
下面使用的是先进先出队列 https://msdn.microsoft.com/en-us/library/7977ey2c(v=vs.110).aspx的基础上实施Stream https://msdn.microsoft.com/en-us/library/system.io.stream(v=vs.110).aspx此处未包含此内容,因为它包含大量样板代码。
X509Certificate2 ServerCertificate = ...;
async Task Test(X509Certificate2 clientCertificate)
{
using (var serverStream = new FullDuplexPipeStream())
using (var clientStream = new FullDuplexPipeStream(serverStream))
using (var sslClientStream = new SslStream(clientStream, false,
(o, x509Certificate, chain, errors) => true,
(o, host, certificates, certificate, issuers) => clientCertificate))
using (var sslServerStream = new SslStream(serverStream, false,
(o, certificate, chain, errors) => true))
{
((Func<Task>)(async () =>
{
try
{
await sslServerStream.AuthenticateAsServerAsync(ServerCertificate,
true, SslProtocols.Tls, false);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}))();
await sslClientStream.AuthenticateAsClientAsync("foobar");
}
}
生成的代码可在 .Net 4.5+ 中运行以重现“Windows 安全”对话框。由于使用了 async/await,它在 .Net 4.0 中不起作用。 (但它可以通过轻微的改变来工作,从而使AuthenticateAsServer()
到不同的线程。)
由于添加了 .Net 4.6,因此要重现的代码要容易得多RSACertificateExtensions.GetRSAPrivateKey() https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.rsacertificateextensions.getrsaprivatekey(v=vs.110).aspx and RSACng.SignHash() https://msdn.microsoft.com/en-us/library/mt132682(v=vs.110).aspx:
clientCertificate.GetRSAPrivateKey()
.SignHash(new byte[20], HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1)
尽管此代码不再提及 SSL,但我相当确定它与 SslStream (或安全通道 https://msdn.microsoft.com/en-us/library/windows/desktop/aa380123(v=vs.85).aspx)正在幕后进行。
进一步的研究
我已经提到过RSACng.SignHash()
** 产生一个看起来非常相似的对话框。好像是调用Win32函数NCryptSignHash() https://msdn.microsoft.com/en-us/library/windows/desktop/aa376295(v=vs.85).aspx.
(** RSACng
仅在 .Net 4.6 及以上版本中可用。基于 CAPI(CryptoAPI)(?)RSA.SignHash()
.Net 4.6 之前可用的对话框显示了一个看起来更传统的对话框。)
查看文档NCryptSignHash()
有这个有趣的花絮NCRYPT_SILENT_FLAG
flag:
请求密钥服务提供商 (KSP) 不显示任何用户界面。如果提供者必须显示 UI 才能进行操作,则调用失败,KSP 应设置NTE_SILENT_CONTEXT
错误代码作为最后一个错误。
此外,文档CRYPT_ACQUIRE_ WINDOWS_HANDLE_FLAG
标记为Crypt获取证书私钥 https://msdn.microsoft.com/en-us/library/windows/desktop/aa379885(v=vs.85).aspx看起来很有希望:
CSP 或 KSP 所需的任何 UI 将是HWND
这是在 pvParameters 参数中提供的。对于 CSP 密钥,使用此标志将导致 CryptSetProvParam 函数具有该标志PP_CLIENT_HWND
使用这个HWND
被称为与NULL
对于 HCRYPTPROV。对于 KSP 密钥,使用此标志将导致 NCryptSetProperty 函数使用NCRYPT_WINDOW_HANDLE_PROPERTY
标志被调用使用HWND
。
不要将此标志与CRYPT_ACQUIRE_SILENT_FLAG
.
你瞧,我认为这NCRYPT_WINDOW_HANDLE_PROPERTY https://msdn.microsoft.com/en-us/library/windows/desktop/aa376242(v=vs.85).aspx#ncrypt_window_handle_property属性,通过设置NCryptSetProperty() https://msdn.microsoft.com/en-us/library/windows/desktop/aa376292(v=vs.85).aspx,这就是我需要解决这个问题的方法。
所以对于可能的解决方案:任何以 HWND 或WPF窗口 https://msdn.microsoft.com/en-us/library/system.windows.window(v=vs.110).aspx并且,可能涉及 P/Invoke,设置NCRYPT_WINDOW_HANDLE_PROPERTY
是一个可行的解决方案。
理想情况下(至少对我来说),代码也应该在 .Net 4.5 中工作。
但是,我在 .Net 4.5 中找不到任何提及 CNG 的内容,因此我认为它很可能涉及 P/Invoking。也许.Net 4.6 中有一个托管解决方案。