views:

767

answers:

3

Hello, think I successfully made a managed wrapperclass to the Credential API functions mentioned here with a little help from there At least the Win32-Errorcodes returned from that functions are Zero or other but expected (i.e. 1168 from CredDelete if calling it twice) and the appropriate values are stored at the correct place in the registry (HKLM/Comm/Security/Credman/1..).

Now I am using the Webbrowser-Control embedded in a WindowsForm on a PPC to authenticate to a Website which uses NTLM-Authentication. I dont want to let a popupdialog appear in which the user has to enter his credentials. Instead I am giving the user the possibilities to store his credentials on the device which he has inputted in an Optiondialog-Form in the first place (intern calling CredWrite/CredUpdate).

But the PIE is given a damn about it what I do with the API, neither CredWrite, -Update or -Delete is actually working. So what am I missing here?

Sample code for CredWrite:

[DllImport("coredll.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int CredWrite([In]IntPtr pCred, [In]CREDWRITE_FLAGS dwflags);

public enum CREDWRITE_FLAGS : int
{
    CRED_FLAG_FAIL_IF_EXISTING = 0x00000400
}

    public struct CRED
    {
        public int dwVersion;
        public CRED_TYPE dwType;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string wszUser;
        public int dwUserLen;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string wszTarget;
        public int dwTargetLen;
        public IntPtr pBlob;
        public int dwBlobSize;
        public CRED_FLAGS dwFlags;
    }
    public enum CRED_TYPE
    {
        CRED_TYPE_NTLM = 0x00010002,
        CRED_TYPE_KERBEROS = 0x00010004,
        CRED_TYPE_PLAINTEXT_PASSWORD = 0x00010006,
        CRED_TYPE_CERTIFICATE = 0x00010008,
        CRED_TYPE_GENERIC = 0x0001000a,
        CRED_TYPE_DOMAIN_PASSWORD = 0x00010001,
    }
    public enum CRED_FLAGS : int
    {
        CRED_FLAG_PERSIST = 0x00000001,
        CRED_FLAG_DEFAULT = 0x00000002,
        CRED_FLAG_SENSITIVE = 0x00000008,
        CRED_FLAG_TRUSTED = 0x00000010
    }

public static void WriteCredentials(string target, string userName, string password)
{
    CRED cred = new CRED();
    cred.dwVersion = 1;
    cred.dwType = CRED_TYPE.CRED_TYPE_NTLM;
    cred.wszTarget = target;
    cred.dwTargetLen = target.Length + 1;
    cred.wszUser = userName;
    cred.dwUserLen = userName.Length + 1; 

    cred.dwBlobSize = (Encoding.Unicode.GetBytes(password).Length + 1) * 2;
    //cred.pBlob = Marshal.StringToCoTaskMemUni(password); //<--not in CF
    //cred.pBlob = Marshal2.StringToHGlobalUni(password); //<--from OpenNETCF, the same?
    cred.pBlob = Marshal.StringToBSTR(password); //<--not sure of that, but tried the other one also
    cred.dwFlags = CRED_FLAGS.CRED_FLAG_PERSIST | CRED_FLAGS.CRED_FLAG_SENSITIVE | CRED_FLAGS.CRED_FLAG_TRUSTED; //<-- results in 25 which is also used in creds read which are stored by the IE-UI-CredMan-dialog

    IntPtr credPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cred));
    Marshal.StructureToPtr(cred, credPtr, true);

    int ret = -1;
    ret = CredWrite(credPtr, CREDWRITE_FLAGS.CRED_FLAG_FAIL_IF_EXISTING); //returns zero, unless called twice with the same target/username-tuple.

    Marshal.FreeHGlobal(credPtr);
}

BTW the so called "MS-Experts" are mentioning that PIE has its own cahing mechanism for creds, and thats why PIE is ignoring the changes on CredUpdate. But I doubt that this is 100% correct, cause when I call CredWrite on a device with no credentials at all, that PIE ignores them too (popups cred-inputdialog).

Can someone assist me in that?

Thnx

A: 

The things I'd check are

  1. If you manually log in using the dialog, does it store anything at the expected credential registry key(HKLM/Comm/Security/Credman/1..)? If not, then I'd say it's pretty strong evidence that it isn't using the Cred Manager.
  2. If you do manual NTLM authentication (with Explorer for example), does the browser pop up the credential dialog?

I have done NTLM authentication with a device before, just not with the browser control and I used the Authentication Services APIs. Here's an example in native. I've been meaning to port it to managed, just haven't found the time.

Edit 1: In further looking at your code, I don't like the blob. What makes you think it should go in as a BSTR? That's a COM type, and I highly doubt it's what you want. I'd be inclined to just send in a byte array with the password. I'd try Unicode first, since CE is heavily biased toward Unicode, then ASCII if that fails.

I also think your marshaling of the CRED structure is suspect, but that's simply because I've been using the CF since pre-1.0 days and I've learned to "trust but verify" everything the marshaler does. I'd at least look at the CRED instance in a memory view and make sure that those string are indeed just 4-byte pointers and that the addresses that they point to actually contain your string data (nothing more and null terminated).

ctacke
A: 

Hello ctacke,

  1. Yes it stores a key X with a correspondig value named "Cred" whem I'm using the dialog ((HKLM/Comm/Security/Credman/Creds/x..) where x is one greater than the last one, if one exists. So does it when I trigger it with a call to CredWrite

  2. Dont know if I understand your question exact. If I have entered the creds in the first place, then the dialog does not appear anymore. I have to call CredDelete or manually delete the key mentioned above AND make a softreset so that the dialog reappears.

Could you tell me if my pinvoke-stuff is correct, especially the line for replacing the "Marshal.StringToCoTaskMemUni and the CRED-Structure.

Meanwhile I discovered a weird thing whilst playling around with the parameters of the CRED-Struct and the Flags. Until recently I always called the function that way:

CredWrite("mydomain", "user1", "1234"); //That doesnt work

now I am doing that:

CredWrite(String.Empty, "mydomain\\user1", "1234");

and changed the line on calling the API-Function to this:

ret = CredWrite(credPtr, 0);

That worked for the first time ever! PIE logged on with that credentials...Hooray... but...on second call with different creds it failed. Not only that PIE uses still the old ones, but on subsequent calls, the line

Marshal.StructureToPtr(cred, credPtr, true);

terminated my debugging session to the device....???

Very frustating...:-((

A: 

Just curious Homer, how did you find the values for the enums? Is it in the WinCE SDK C++ source code files?

I have a slightly different layout than what you have (mainly adding the error codes for the return value of the function itself & not IntPtr for the cred argument, but rather [In] ref CRED cred instead), and haven't tested mine to see if it works. Mainly trying to enumerate everything as precisely as I can get it.

UPDATE: Nevermind, I found the error codes and all the other enumerations proper. It just so happens I was getting a NotSupportedException previously when I had it returning ERROR_CODE as opposed to int for HRESULT, but now I'm getting an access violation (native code: 0xc0000005), and I think my own marshalling of the structs/calculations of the blob values/etc. could be wrong.

Ryan
Okay, just got my version working, and the only thing I see different is that the target is not supposed to point to anything if you want this to update the default credentials. wszTarget should be "".
Ryan