Setting the “Windows Security” dialog owner during a Client Certificate involving SslStream.AuthenticateAsClient()

梦想的初衷 提交于 2019-12-08 00:44:29

问题


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:

  • The SSL server to which the client is attempting to connect to requested a client certificate as part of the TLS/SSL handshake.
  • The X509Certificate passed via the second argument of SslStream.AuthenticateAsClient() or via the LocalCertificateSelectionCallback callback (given when constructing SslStream) was strongly protected. (The dialog itself will vary in appearance depending on the so call Security Level of the protection.)

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.


回答1:


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



来源:https://stackoverflow.com/questions/37785820/setting-the-windows-security-dialog-owner-during-a-client-certificate-involvin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!