在调用SslStream.AuthenticateAsClient()以启动TLS / SSL握手时,可以向用户显示以下内容" Windows安全"对话框:
Windows安全性:此应用程序需要使用加密密钥
如果满足以下两个原因,就会发生这种情况:
在Windows的更高版本(Win8和Win10)上,此对话框由名为" CredentialUIBroker.exe "的外部程序显示。由 svchost.exe 调用的。在早期版本中,它由加载到正在运行的程序中的dll呈现:Win7中的 comctl32.dll 和WinXP中的 cryptui.dll
虽然此Windows安全对话框似乎是一个模态对话框,但它的行为更像是没有所有者参数集的模态对话框。
这会导致以下问题:
所以问题是:如何进行设置,以便将Windows安全对话框显示为模式对话框?
Chrome遇到此问题,并且未修复至今(Chrome 51)(错误跟踪:https://bugs.chromium.org/p/chromium/issues/detail?id=304152)
Internet Explorer不会遇到此问题。它将Windows安全性对话框显示为模式对话框。
Firefox不适用,因为它从未使用过Windows'证书商店,而不是依靠自己的存储。
让Windows安全UI显示有点参与。
首先,它需要在导入UI期间选中强保护选项导入的证书。 (另请注意:使用的任何证书也应该是不可导出的,因为仅适用于可导出证书的解决方案不适合生产。)
以下此代码还需要服务器证书(任何证书没有强保护都可以),因为我们在虚假的TLS / SSL连接中使用SslStream.AuthenticateAsClientAsync()
。
此外,下面使用的FullDuplexPipeStream
是基于FIFO queue的Stream实现,此处未包含此内容,因为它有很多样板编码。
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+中运行,以重现&#34; Windows安全&#34;对话。由于使用了async / await,它在.Net 4.0中不起作用。 (但可以通过稍微改动将AuthenticateAsServer()
挂在不同的线程上。)
由于添加了RSACertificateExtensions.GetRSAPrivateKey()和RSACng.SignHash(),在.Net 4.6中重现的代码要容易得多:
clientCertificate.GetRSAPrivateKey()
.SignHash(new byte[20], HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1)
虽然此代码不再提及SSL,但我确信它与SslStream(或Secure Channel)在幕后所做的事情相同。
我已经提到 RSACng.SignHash()
**会产生一个非常相似的对话框。它似乎在调用Win32函数NCryptSignHash()。
(** RSACng
仅在.Net 4.6以后可用。在.Net 4.6之前可用的CAPI(CryptoAPI)基础(?)RSA.SignHash()
显示了更具传统外观的对话框。)
查看NCryptSignHash()
的文档有关于NCRYPT_SILENT_FLAG
标志的这个有趣的消息:
请求密钥服务提供商(KSP)不显示任何用户界面。如果提供者必须显示要操作的UI,则调用失败,KSP应将
NTE_SILENT_CONTEXT
错误代码设置为最后一个错误。
此外,CryptAcquireCertificatePrivateKey CRYPT_ACQUIRE_ WINDOWS_HANDLE_FLAG
标志的文档看起来很有希望:
CSP或KSP所需的任何UI都是pvParameters参数中提供的
HWND
的子级。对于CSP密钥,使用此标志将导致使用此PP_CLIENT_HWND
的标记HWND
的CryptSetProvParam函数与NULL
一起调用HCRYPTPROV。对于KSP密钥,使用此标志将导致使用NCRYPT_WINDOW_HANDLE_PROPERTY
调用带有HWND
标志的NCryptSetProperty函数。 请勿将此标记与CRYPT_ACQUIRE_SILENT_FLAG
一起使用。
而且,我认为通过NCRYPT_WINDOW_HANDLE_PROPERTY设置的NCryptSetProperty()属性是我解决此问题所需要的。
因此,对于可能的解决方案:任何以HWND或WPF Window开头的代码,可能涉及P / Invoke,设置NCRYPT_WINDOW_HANDLE_PROPERTY
都是可行的解决方案。
理想情况下(至少对我而言),代码也应该适用于.Net 4.5。
但是,我在.Net 4.5中找不到CNG的提及,所以我认为它很可能涉及P / Invoking。也许在.Net 4.6中有一个托管解决方案。
答案 0 :(得分:0)
.Net 4.6中的修复(有些)实际上非常简单:
var rsa = (RSACng)clientCertificate.GetRSAPrivateKey();
rsa.Key.ParentWindowHandle = MyForm.Handle;
rsa.SignHash(new byte[20], HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
(自从.Net 4.0以来我一直在关注这个问题,谁知道.Net 4.6会让它变得如此简单!)
请注意,这只是某种修复程序,因为它无法直接解决SslStream.AuthenticateAsClient()
显示的UI问题。但是,通过在 SslStream.AuthenticateAsClient()
之前执行上述,CNG会缓存用户的授权,并且在TLS / SSL握手期间不会显示该对话框。
(这就是为什么解决方案需要基于CNG而不是旧的CAPI。)
此缓存的保留是通过组策略配置的,因此这可能不适用于所有环境。
不幸的是,我仍然需要支持.Net 4.5,因此非常感谢解决方案