ygoe
ygoe

Reputation: 20384

CredUIPromptForCredentials from .NET with SecureString

I'd like to show the standard system dialog to ask the user for an account username and password to use this information to start a process with these credentials.

I've been pointed to the CredUIPromptForCredentials function that shows that dialog. It returns username and password as string. But the ProcessStartInfo structure expects a password as SecureString.

I understand that I could now use the password as string and convert it to a SecureString character by character (there's no single function for that) - but it would defeat the idea behind the SecureString entirely.

So I guess there must be some way to directly accept the password from the unmanaged call to CredUIPromptForCredentials as SecureString in .NET. After all, I really don't need to access the password in my application in any way. It's just supposed to be used to start another process and can then be forgotten as soon as possible.

So how would my P/Invoke declaration for CredUIPromptForCredentials look like with a SecureString? (I've started with the one from pinvoke.net for C#.)

Update: Oh, and if somebody has an example for the new function CredUIPromptForWindowsCredentials in Windows Vista/7, that would be cool as well, because I can't even figure out how to use that at the moment.

Upvotes: 1

Views: 1815

Answers (1)

Mitch
Mitch

Reputation: 22261

You can cast the IntPtr of an unmanaged string buffer to char* and use the SecureString(char*, int) constructor.

// somehow, we come into posession of an IntPtr to a string
// obviously, this would be a foolish way to come into it in
// production, since stringOriginalContents is already in managed
// code, and the lifetime can therefore not be guaranteed...
var stringOriginalContents = "foobar";
IntPtr strPtr = Marshal.StringToHGlobalUni(stringOriginalContents);
int strLen = stringOriginalContents.Length;
int maxLen = 100;

// we copy the IntPtr to a SecureString, and zero out the old location
SecureString ssNew;
unsafe
{
    char* strUPtr = (char*)strPtr;

    // if we don't know the length, calculate
    //for (strLen = 0; *(strUPtr + strLen) != '\0' 
    //    // stop if the string is invalid
    //    && strLen < maxLen; strLen++)
    //    ;

    ssNew = new SecureString((char*)strPtr, strLen);

    // zero out the old memory and release, or use a Zero Free method
    //for (int i = 0; i < strLen; i++)
    //    *(strUPtr + i) = '\0';
    //Marshal.FreeHGlobal(strPtr);
    // (only do one of these)
    Marshal.ZeroFreeGlobalAllocUnicode(strPtr);
}

// now the securestring has the protected data, and the old memory has been
// zeroed, we can check that the securestring is correct.  This, also should
// not be in production code.
string strInSecureString =
    Marshal.PtrToStringUni(
    Marshal.SecureStringToGlobalAllocUnicode(ssNew));
Assert.AreEqual(strInSecureString, stringOriginalContents);

Upvotes: 2

Related Questions