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

2024-01-15

首先一些背景

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


A fix (somewhat)在.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 会让这个问题变得如此简单!)

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

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

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

不幸的是,我仍然需要支持 .Net 4.5,因此我们将不胜感激

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在涉及 SslStream.AuthenticateAsClient() 的客户端证书期间设置“Windows 安全”对话框所有者 的相关文章

随机推荐