设置" Windows安全"涉及SslStream.AuthenticateAsClient()

时间:2016-06-13 09:12:44

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

首先是背景

在调用SslStream.AuthenticateAsClient()以启动TLS / SSL握手时,可以向用户显示以下内容" Windows安全"对话框:

enter image description here

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

如果满足以下两个原因,就会发生这种情况:

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

问题

虽然此Windows安全对话框似乎是一个模态对话框,但它的行为更像是没有所有者参数集的模态对话框

这会导致以下问题:

  • 对话框可以(并且经常会)在正在运行的程序的窗口后面打开,从而使自己难以找到用户。
  • 对话框可以隐藏在后台,用户点击正在运行的程序的其他窗口,导致混淆。
  • 当对话框打开时,正在运行的程序的其他窗口上的UI元素不会被冻结,用户可以自由地执行其他操作。

所以问题是:如何进行设置,以便将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 queueStream实现,此处未包含此内容,因为它有很多样板编码。

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中有一个托管解决方案。

1 个答案:

答案 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,因此非常感谢解决方案