3
\$\begingroup\$

I have some code that requires the user (an admin) elevate to a system admin account to perform a task. I wish to prompt the user for the admin credentials to use. I believe returning a NetworkCredential provides a "protected" (SecureString) password (assuming I dispose correctly).

The concern is retrieving a NetworkCredential for a user other than the currently logged in user.

I've created a class that will provide a Managed C# call via P/invoke to Windows C CredUIPromptForWindowsCredentials. I believe this should create a Windows system dialog which prompts for the user domain/id and password I intend to use for elevating.

This returns a (pinned) struct that must be decoded via CredUnPackAuthenticationBuffer.

unfortunatley, CredUnPackAuthenticationBuffer returns a LPTSTR pszPassword, the best I have been able to find (so far) is to drop that into a StringBuilder which I later clear. Ideally, this would be returned as a SecureString, or something pinned which I could zero later...

Thoughts? suggestions? improvements?

using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;


namespace MyNamespace 
{
  class CredentialsPrompt
  {
// Zeros Pinned (non managed) memory buffers
[DllImport("dllmain.dll", CharSet = CharSet.Auto,SetLastError = true)]
private static extern IntPtr SecureZeroMem(IntPtr ptr,uint cnt);
// 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct CREDUI_INFO
{
    public int cbSize;
    public IntPtr hwndParent;
    public string pszMessageText;
    public string pszCaptionText;
    public IntPtr hbmBanner;
} 
// 
[DllImport("credui.dll", CharSet = CharSet.Auto)]
private static extern bool CredUnPackAuthenticationBuffer(int dwFlags,
                                                           IntPtr pAuthBuffer,
                                                           uint cbAuthBuffer,
                                                           StringBuilder pszUserName,
                                                           ref int pcchMaxUserName,
                                                           StringBuilder pszDomainName,
                                                           ref int pcchMaxDomainame,
                                                           StringBuilder pszPassword,
                                                           ref int pcchMaxPassword);
// Decode credentials
[DllImport("credui.dll", CharSet = CharSet.Auto)]
private static extern int CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere,
                                                             int authError,
                                                             ref uint authPackage,
                                                             IntPtr InAuthBuffer,
                                                             uint InAuthBufferSize,
                                                             out IntPtr refOutAuthBuffer,
                                                             out uint refOutAuthBufferSize,
                                                             ref bool fSave,
                                                             int flags);


//
public static void GetCredentialsVistaAndUp(string serverName, out NetworkCredential networkCredential)
{
    CREDUI_INFO credui = new CREDUI_INFO();
    credui.pszCaptionText = "Please enter the credentails for " + serverName;
    credui.pszMessageText = "DisplayedMessage";
    credui.cbSize = Marshal.SizeOf(credui);

    uint authPackage = 0;
    IntPtr outCredBuffer = new IntPtr();
    uint outCredSize;
    bool save = false;

    var usernameBuf = new StringBuilder(100);
    var passwordBuf  = new StringBuilder(100);
    var domainBuf = new StringBuilder(100);

    int maxUserName = 100;
    int maxDomain = 100;
    int maxPassword = 100;

    int result = CredUIPromptForWindowsCredentials(ref credui,
                                                   0,
                                                   ref authPackage,
                                                   IntPtr.Zero,
                                                   0,
                                                   out outCredBuffer,
                                                   out outCredSize,
                                                   ref save,
                                                   0x1000 );   /* CREDUIWIN_SECURE_PROMPT */

    try 
    {
      if (result == 0)
      {
        try 
        {
          if (CredUnPackAuthenticationBuffer(0, 
                                               outCredBuffer, 
                                               outCredSize, 
                                               usernameBuf, 
                                               ref maxUserName,
                                               domainBuf, 
                                               ref maxDomain, 
                                               passwordBuf, 
                                               ref maxPassword))
            {
                networkCredential = new NetworkCredential(
                                            userName: usernameBuf.ToString(),
                                            password: passwordBuf.ToString(),
                                            domain:  domainBuf.ToString()
                                        );
            }
        }
        finally 
        {
          passwordBuf.Clear();
            return;
        }
      } /* if (result == 0) */
    }                                              
    finally 
    {
      Marshal.ZeroFreeGlobalAllocUnicode(outCredBuffer);

    }
    networkCredential = null;
} /* end GetCredentialsVistaAndUp */
} /* end Class */
} /* end Namespace */
\$\endgroup\$
7
  • \$\begingroup\$ Have you Tested the code? \$\endgroup\$ – Malachi Sep 24 '14 at 20:09
  • \$\begingroup\$ Code functions. Returns a NetworkCredential. Does not crash. I haven't walked the heap while running to try and find a password. \$\endgroup\$ – skipmcne Sep 24 '14 at 20:12
  • \$\begingroup\$ Awesome, from what you wrote it sounded like you hadn't run it yet is all. \$\endgroup\$ – Malachi Sep 24 '14 at 20:17
  • \$\begingroup\$ How could it work when it doesn't even compile? \$\endgroup\$ – svick Sep 25 '14 at 20:56
  • 2
    \$\begingroup\$ @Malachi Because you can't return out of a finally. Yeah, it's trivial to fix it, but it shows this is not real code that works as is. \$\endgroup\$ – svick Sep 25 '14 at 22:08
3
\$\begingroup\$

The code indentation is pretty horrible, you should really indent it correctly, it will help you (and anyone who read your code) to understand it and it would let you remove the comments saying which opening bracket is the closing one for.

Also, your class doesn't have any visibility modifier, meaning it will take the default : internal. If this is what you want, you should specify it. Default values will always bring up the question : "Was it intentional or not?".

I'm not sure why you use try/finally inside your if. Which exceptions can throw CredUnPackAuthenticationBuffer and why don't you catch them?

In your NetworkCredential constructor, you specify the parameter names, this isn't necessary. I think it makes the code heavier. People know wants to know what are the parameters of the constructor will be able to check it by themselves.

In your CredUIPromptForWindowsCredentials call, you use 0x1000, and there's a comment beside, telling what is the signification of the number. You should create a constant for this number, instead of having a comment beside it.

Your struct CREDUI_INFO doesn't respect the C# naming conventions (the PascalCase), I guess you knew it already since the rest of your code seems fine about this.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ When writing interop code, it makes sense to me to follow naming conventions of the original code. \$\endgroup\$ – svick Sep 25 '14 at 21:00

Not the answer you're looking for? Browse other questions tagged or ask your own question.