设置“Windows 安全"涉及 SslStream.AuthenticateAsClient() 的客户端证书期间的对话所有者 [英] Setting the "Windows Security" dialog owner during a Client Certificate involving SslStream.AuthenticateAsClient()

查看:33
本文介绍了设置“Windows 安全"涉及 SslStream.AuthenticateAsClient() 的客户端证书期间的对话所有者的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

先介绍一下背景

调用

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

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

在更高版本的 Windows(Win8 和 Win10)上,此对话框由名为CredentialUIBroker.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)

  • Internet Explorer 没有这个问题.它将 Windows 安全对话框显示为模式对话框.

  • Firefox 不适用,因为它从未使用过 Windows 的证书存储,而是依赖于自己的存储.

重现代码

显示 Windows 安全用户界面有点复杂.

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

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

此外,下面使用的 FullDuplexPipeStream 是一个 FIFO 队列基于Stream 未包含在此处,因为它包含大量样板代码.

X509Certificate2 ServerCertificate = ...;异步任务测试(X509Certificate2 clientCertificate){使用 (var serverStream = new FullDuplexPipeStream())使用 (var clientStream = new FullDuplexPipeStream(serverStream))使用 (var sslClientStream = new SslStream(clientStream, false,(o, x509Certificate, chain, errors) =>真的,(o, 主机, 证书, 证书, 颁发者) =>客户证书))使用 (var sslServerStream = new SslStream(serverStream, false,(o,证书,链,错误)=>真的)){((Func)(async() =>{尝试{等待 sslServerStream.AuthenticateAsServerAsync(ServerCertificate,真,SslProtocols.Tls,假);}捕获(异常前){Console.WriteLine(ex);}}))();等待 sslClientStream.AuthenticateAsClientAsync("foobar");}}

生成的代码适用于 .Net 4.5+ 以重现Windows 安全"对话框.由于使用了 async/await,它在 .Net 4.0 中不起作用.(但可以通过轻微的改动将 AuthenticateAsServer() 连接到不同的线程上.)

由于添加了 RSACertificateExtensions.GetRSAPrivateKey()RSACng.SignHash():

clientCertificate.GetRSAPrivateKey().SignHash(新字节[20], HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1)

尽管此代码不再提及 SSL,但我相当确定它与 SslStream(或 Secure Channel) 在幕后进行.

进一步研究

我已经提到了 RSACng.SignHash() ** 生成一个外观非常相似的对话框.它似乎在调用 Win32 函数 NCryptSignHash().

(** RSACng 仅在 .Net 4.6 以后可用.基于 CAPI (CryptoAPI) (?) RSA.SignHash() 在 .Net 之前可用4.6 显示了一个看起来更传统的对话框.)

查看 NCryptSignHash() 的文档有关于 NCRYPT_SILENT_FLAG 标志的有趣花絮:

<块引用>

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

此外,CryptAcquireCertificatePrivateKey 看起来很有希望:

<块引用>

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

和 lo-and-behond,我认为这 NCRYPT_WINDOW_HANDLE_PROPERTY 属性,通过 NCryptSetProperty(),是我解决这个问题所需要的.

因此对于可能的解决方案:任何以 HWND 或 WPF 窗口,并且可能涉及 P/Invoke,设置 NCRYPT_WINDOW_HANDLE_PROPERTY 是一个可行的解决方案.

理想情况下(至少对我而言),代码也应该在 .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(新字节[20], HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);

(实际上我从 .Net 4.0 开始就一直在研究这个问题,谁知道 .Net 4.6 会让它变得如此简单!)

注意,这只是某种程度上的修复,因为它不能直接解决SslStream.AuthenticateAsClient() 显示的 UI 的问题.但是,通过执行上述 before SslStream.AuthenticateAsClient(),CNG 会缓存用户的授权,并且在 TLS/SSL 握手期间不会显示对话框.

(这就是为什么解决方案需要基于 CNG 而不是旧的 CAPI.)

此缓存的保留是通过组策略配置的,因此这可能不适用于所有环境.

不幸的是,我仍然需要支持 .Net 4.5,因此非常感谢提供解决方案

Some background first

Upon calling SslStream.AuthenticateAsClient() to initiate TLS/SSL handshake, the user can be presented with this following "Windows Security" dialog:

Windows security: This application needs to use a cryptographic key

This happens when both these following reasons are met:

On later versions of Windows (Win8 and Win10), this dialog is presented by an external program called "CredentialUIBroker.exe" that's invoked by svchost.exe. On earlier versions, it was presented by dlls loaded into the running program itself: comctl32.dll in Win7 and cryptui.dll in WinXP

The problem

While this Windows Security dialog appears to be a modal dialog, it behaves more like a modal dialog without the owner parameter set.

This causes the following problems:

  • The dialog can (and often does) open behind windows of the running program, thereby making itself hard to spot for the user.
  • The dialog can be hidden in the background, by the user clicking on other windows of the running program, leading to confusion.
  • UI elements on other windows of the running program aren't frozen while the dialog is open, and the user is free to perform other actions.

So the question is: How does one set things up so that the Windows Security dialog is presented as a modal dialog?

The problem as seen in other software

  • Chrome suffers from this problem and is not fixed to date (Chrome 51) (Bug track: https://bugs.chromium.org/p/chromium/issues/detail?id=304152)

  • Internet Explorer does not suffer from this problem. It presents the Windows Security dialog as a modal dialog.

  • Firefox is non-applicable as it has never used Windows' Certificate stores, relying instead on its own storage.

Code to reproduce

Getting that Windows Security UI to show is a bit involved.

First, it requires a certificate that was imported with the strong protection option checked during the import UI. (On a side note: Any certificate used should also be non-exportable, because a solution that only works for exportable certificates is unsuitable for production.)

This code below also requires a server certificate (any certificate without strong protection will do), because we're using SslStream.AuthenticateAsClientAsync() in a fake TLS/SSL connection.

Moreover, FullDuplexPipeStream used below is a FIFO queue based implementation of Stream that isn't included here because it's A LOT of boilerplate coding.

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");
    }
}

The resulting code works in .Net 4.5+ to reproduce the "Windows Security" dialog. It will not work in .Net 4.0 due to the use of async/await. (But it can be made to work with a slight alteration that hitches AuthenticateAsServer() onto a different thread.)

The code to reproduce is much easier in .Net 4.6 due to the addition of RSACertificateExtensions.GetRSAPrivateKey() and RSACng.SignHash():

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

Although this code no longer has any mention of SSL, I'm fairly sure it's the same thing as what SslStream (or Secure Channel) is doing behind the scenes.

Further research

I've already mentioned RSACng.SignHash() ** produces a very similar looking dialog. It seems to be calling the Win32 function NCryptSignHash().

(** RSACng is only available in .Net 4.6 onwards. CAPI (CryptoAPI) based (?) RSA.SignHash() that was available prior to .Net 4.6 shows a more legacy-looking dialog.)

A look at the documentation for NCryptSignHash() has this interesting tidbit about the NCRYPT_SILENT_FLAG flag:

Requests that the key service provider (KSP) not display any user interface. If the provider must display the UI to operate, the call fails and the KSP should set the NTE_SILENT_CONTEXT error code as the last error.

Furthermore, documentation to the CRYPT_ACQUIRE_ WINDOWS_HANDLE_FLAG flag for CryptAcquireCertificatePrivateKey looks promising:

Any UI that is needed by the CSP or KSP will be a child of the HWND that is supplied in the pvParameters parameter. For a CSP key, using this flag will cause the CryptSetProvParam function with the flag PP_CLIENT_HWND using this HWND to be called with NULL for HCRYPTPROV. For a KSP key, using this flag will cause the NCryptSetProperty function with the NCRYPT_WINDOW_HANDLE_PROPERTY flag to be called using the HWND. Do not use this flag with CRYPT_ACQUIRE_SILENT_FLAG.

And lo-and-behond, I think this NCRYPT_WINDOW_HANDLE_PROPERTY property, set via NCryptSetProperty(), is what I need to fix this problem.

So for possible solution: Any code that starts with an HWND or a WPF Window and, possibly involving P/Invoke, sets NCRYPT_WINDOW_HANDLE_PROPERTY is a viable solution.

Ideally (for me at least), the code should also work in .Net 4.5.

However, I couldn't find any mention of CNG in .Net 4.5 so I think it will most likely involve P/Invoking. Maybe there's a managed solution in .Net 4.6.

解决方案

A fix (somewhat) in .Net 4.6 is actually quite simple:

var rsa = (RSACng)clientCertificate.GetRSAPrivateKey();
rsa.Key.ParentWindowHandle = MyForm.Handle;

rsa.SignHash(new byte[20], HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);

(I've actually been looking at this problem since .Net 4.0 and who knew .Net 4.6 would make it so easy!)

Note, this is only somewhat of a fix because it doesn't directly solve the problem for UIs shown by SslStream.AuthenticateAsClient(). However, by performing the above before SslStream.AuthenticateAsClient(), CNG caches the user's grant and the dialog is not shown during the TLS/SSL handshake.

(This is why the solution needs to be CNG based instead of the older CAPI.)

The retention of this cache is configured via group policy, so this may not work in every environment.

Unfortunately, I still need to support .Net 4.5, so a solution there would be greatly appreciated

这篇关于设置“Windows 安全"涉及 SslStream.AuthenticateAsClient() 的客户端证书期间的对话所有者的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆