c# - 在涉及SslStream.AuthenticateAsClient()的客户端证书期间设置“Windows安全性”对话框所有者

标签 c# .net wpf .net-4.5 .net-4.6

首先有一些背景

调用SslStream.AuthenticateAsClient()发起TLS / SSL握手后,可以在下面的“ Windows安全性”对话框中向用户显示:

enter image description here

Windows安全性:此应用程序需要使用加密密钥

当同时满足以下两个原因时,就会发生这种情况:


客户端尝试连接到的SSL服务器请求客户端证书,这是TLS / SSL握手的一部分。
通过X509Certificate的第二个参数或通过SslStream.AuthenticateAsClient()回调(给定LocalCertificateSelectionCallback时)给出的constructing SslStream。 (对话框本身的外观会有所不同,具体取决于所谓的保护的“安全级别”。)


在Windows的更高版本(Win8和Win10)上,此对话框由名为svchost.exe调用的外部程序“ CredentialUIBroker.exe”显示。在早期版本中,它是由加载到正在运行的程序本身的dll呈现的:Win7中的comctl32.dll和WinXP中的cryptui.dll

问题

虽然此Windows安全性对话框看起来像是模式对话框,但其行为更像是没有设置所有者参数的模式对话框。

这将导致以下问题:


该对话框可以(并且经常)在正在运行的程序的窗口后面打开,从而使用户很难发现它自己。
用户可以通过单击正在运行的程序的其他窗口将对话框隐藏在后台,从而引起混乱。
对话框打开时,不会冻结正在运行的程序的其他窗口上的UI元素,用户可以自由执行其他操作。


因此,问题是:如何进行设置,以使Windows安全对话框显示为模式对话框?

在其他软件中看到的问题


Chrome遭受此问题的困扰,但尚未修复(Chrome 51)(错误轨道:strongly protected
Internet Explorer不会遇到此问题。它将Windows安全性对话框显示为模式对话框。
Firefox是不适用的,因为它从未使用过Windows的证书存储,而是依靠其自己的存储。


复制代码

要显示该Windows安全性UI有点麻烦。

首先,它需要使用导入UI期间选中了“强保护”选项的证书。 (附带说明:使用的任何证书也应不可出口,因为仅适用于可出口证书的解决方案不适用于生产。)

下面的代码也需要服务器证书(任何没有强力保护的证书都可以),因为我们在伪造的TLS / SSL连接中使用SslStream.AuthenticateAsClientAsync()

此外,下面使用的FullDuplexPipeStreamhttps://bugs.chromium.org/p/chromium/issues/detail?id=304152的基于FIFO queue的实现,由于很多样板代码,因此此处未包括在其中。

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()挂接到其他线程上。)

由于添加了StreamRSACertificateExtensions.GetRSAPrivateKey(),在.Net 4.6中重现代码要容易得多:

clientCertificate.GetRSAPrivateKey()
    .SignHash(new byte[20], HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1)


尽管此代码不再提及SSL,但我相当确定这与SslStream(或RSACng.SignHash())在后台进行的操作相同。

进一步的研究

我已经提到RSACng.SignHash() **会产生一个非常相似的对话框。似乎正在调用Win32函数Secure Channel

(** RSACng仅在.Net 4.6及更高版本中可用。.Net4.6之前可用的基于CAPI(CryptoAPI)的(?)RSA.SignHash()显示了一个外观更传统的对话框。)

看一下NCryptSignHash()的文档,有关于NCRYPT_SILENT_FLAG标志的这个有趣的花絮:


  请求密钥服务提供者(KSP)不显示任何用户界面。如果提供者必须显示UI才能操作,则调用将失败,并且KSP应将NTE_SILENT_CONTEXT错误代码设置为最后一个错误。


此外,NCryptSignHash()CRYPT_ACQUIRE_ WINDOWS_HANDLE_FLAG标志的文档看起来很有希望:


  CSP或KSP所需的任何UI都是pvParameters参数中提供的HWND的子级。对于CSP密钥,使用此标志将导致对带有HCRYPTPROV的标志PP_CLIENT_HWND的CryptSetProvParam函数使用该HWND进行调用。对于KSP密钥,使用此标志将导致使用NULL调用带有NCRYPT_WINDOW_HANDLE_PROPERTY标志的NCryptSetProperty函数。
  不要将此标志与HWND一起使用。


而且,我认为通过CryptAcquireCertificatePrivateKey设置的NCRYPT_WINDOW_HANDLE_PROPERTY属性是解决此问题的必要条件。

因此,对于可能的解决方案:任何以HWND或NCryptSetProperty()开头并且可能涉及P / Invoke的代码设置CRYPT_ACQUIRE_SILENT_FLAG都是可行的解决方案。

理想情况下(至少对我而言),该代码也应在.Net 4.5中运行。

但是,我在.Net 4.5中找不到CNG的任何提及,因此我认为它很可能涉及P / Invoking。 NET 4.6中可能存在托管解决方案。

最佳答案

.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,因此不胜感激。

关于c# - 在涉及SslStream.AuthenticateAsClient()的客户端证书期间设置“Windows安全性”对话框所有者,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37785820/

相关文章:

C# 动态字段取决于 "T"类型,没有硬编码

c# - 来自列表的 WPF Listview 数据绑定(bind)

c# - 即使属性值未更改,也会触发 Propertychanged 事件

c# wpf comboBox 在我输入第一个字母时选择列表中的项目

c# - 软键盘资本初

c# - Entity Framework 的连接字符串

c# - System.Threading.Task 不工作

.net - 如何调试 WCF 问题?

c# - 检查数组中的多个元素是否包含相同的值

.net - 如何在 VSTS 构建任务期间执行 .Net 应用程序?