Search code examples
c#pinvokecredential-manager

Cannot save password to credential manager when encrypted


I'm trying to save an encrypted password to the Windows Credential manager, the below code works fine for read, and I can produce a correctly encrypted string however the encrypted form of the password is never persisted.

For completeness, I need to encrypt the password as the client application requires this (Office 2010). If I save the password via Office 2010 I can then read it correctly.

Credential Structure

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct Credential
{
  public UInt32 flags;
  public UInt32 type;
  public string targetName;
  public string comment;
  public System.Runtime.InteropServices.ComTypes.FILETIME lastWritten; 
  public UInt32 credentialBlobSize;
  public IntPtr credentialBlob;
  public UInt32 persist;
  public UInt32 attributeCount;
  public IntPtr credAttribute;
  public string targetAlias;
  public string userName;
}

Read:

IntPtr credPtr;
if (!Win32.CredRead(target, settings.Type, 0, out credPtr))
{
  Trace.TraceError("Could not find a credential with the given target name");
  return;
}

var passwordBytes = new byte[blobSize];
Marshal.Copy(blob, passwordBytes, 0, (int)blobSize);

var decrypted = ProtectedData.Unprotect(passwordBytes, null, DataProtectionScope.CurrentUser);
return Encoding.Unicode.GetString(decrypted);

Write:

var bytes = Encoding.Unicode.GetBytes(password);
var encypted = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);

//construct and set all the other properties on Credential...

credential.credentialBlobSize = (uint)bytes.Length;
credential.credentialBlob = GetPtrToArray(bytes);

if (!Win32.CredWrite(ref credential, 0))
  throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

GetPtrToArray

 private static IntPtr GetPtrToArray(byte[] bytes)
 {
   var handle = Marshal.AllocHGlobal(bytes.Length);
   Marshal.Copy(bytes, 0, handle, bytes.Length);
   return handle;
 }

The things I have tried are:

  1. Changing credentialBlob to a byte[], this fails with a marshalling error in PtrToStructure during CredentialRead
  2. Changing credentialBlob to a string, and using Unicode.GetBytes() before decrypting, this yields a 2 character string which presume is the contents of the IntPtr

I believe the problem is with sharing the memory for the byte[], i.e. the method of producing a IntPtr to be used with CredWrite(). When attempting to read the credential afterwards, the blobSize and blob are both 0 (i.e. a null ptr to the blob).

For completeness, this is running from a .net 4.6.1 codebase and I can store unencrypted strings (using Marshal.StringToCoTaskMemUni(password)) without any issue.

Can you help?


Solution

  • Turns out I forgot that Credential was a struct and the following lines were in a separate function (simplified above) hence the fields never persisted their values.

    void SetPasswordProperties(Win32.Credential credential)
    {
      credential.credentialBlobSize = (uint)bytes.Length;
      credential.credentialBlob = GetPtrToArray(bytes);
    }
    

    Using a ref parameter fixed this issue like so:

    void SetPasswordProperties(ref Win32.Credential credential)