2012-04-06 17 views
1

Ich habe derzeit ein Programm geschrieben in C# Aufruf CredRead/CredWrite from Advapi32.dll, um es einfach für Benutzer, die nicht mit der Domäne beizutreten, Terminal Server/WebDAV-Server-Anmeldeinformationen speichern. (http://msdn.microsoft.com/en-us/library/aa374788%28v=vs.85%29.aspx für Anmeldeinformationen Strukturinformationen)C# pinvoking CredWrite Problem unter Windows XP Pro

Das ist alles in Ordnung, aber ich lief ein Problem neulich, das auf allen XP-Betriebssystemen besteht; Ich kann die Anmeldeinformationen des Terminalservers nicht speichern. Die systemeigene CredWrite-Methode gibt einen Fehlercode 87 (ERROR_INVALID_PARAMETER) zurück.

Nach dem Ausprobieren zahlreicher verschiedener Kombinationen von Persistenz und Typen von Anmeldeinformationen erkannte ich, dass das Problem der TargetName selbst war. Zum Speichern von Terminalservern im Windows-Schlüsselring lautet der Zielname TERMSRV/server.domain.com. Unter Windows Vista oder höher funktioniert das mit meinem Code, aber unter Windows XP funktioniert das nicht.

Seltsamerweise, wenn Sie die Remotedesktopanwendung unter Windows XP ausführen und die Anmeldeinformationen speichern lassen, geschieht dies ohne großen Aufwand. Als ich eine Methode zum Aufzählen gespeicherter Anmeldedaten nach der Verwendung von RDP erstellte (offensichtlich wurden keine Passwort-Blobs zurückgegeben), konnte ich sehen, dass das Ziel mit dem Ziel identisch war, das mein Programm zu schreiben versuchte.

Um zu bestätigen, dass das Problem in der TargetName war, entfernte ich das "/" und es hat gut funktioniert.

Unten ist der relevante Teil meines Codes:

var writeInt = NativeCredMan.WriteCred("TERMSRV/host.server.com", samAccountName, password, CRED_TYPE.DOMAIN_PASSWORD, CRED_PERSIST.LOCAL_MACHINE); 

... ...

public enum CRED_TYPE : uint 
{ 
    GENERIC = 1, 
    DOMAIN_PASSWORD = 2, 
    DOMAIN_CERTIFICATE = 3, 
    DOMAIN_VISIBLE_PASSWORD = 4, 
    GENERIC_CERTIFICATE = 5, 
    DOMAIN_EXTENDED = 6, 
    MAXIMUM = 7,  // Maximum supported cred type 
    MAXIMUM_EX = (MAXIMUM + 1000), // Allow new applications to run on old OSes 
} 

public enum CRED_PERSIST : uint 
{ 
    SESSION = 1, 
    LOCAL_MACHINE = 2, 
    ENTERPRISE = 3, 
} 

... ...

[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] 
    static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag, out IntPtr CredentialPtr); 

    [DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)] 
    static extern bool CredWrite([In] ref NativeCredential userCredential, [In] UInt32 flags); 

    [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] 
    static extern bool CredFree([In] IntPtr cred); 

    [DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)] 
    static extern bool CredDelete(string target, CRED_TYPE type, int flags); 

    //[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)] 
    //static extern bool CredEnumerateold(string filter, int flag, out int count, out IntPtr pCredentials); 

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 
    public static extern bool CredEnumerate(string filter, uint flag, out uint count, out IntPtr pCredentials); 

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
    private struct NativeCredential 
    { 
     public UInt32 Flags; 
     public CRED_TYPE Type; 
     public IntPtr TargetName; 
     public IntPtr Comment; 
     public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; 
     public UInt32 CredentialBlobSize; 
     public IntPtr CredentialBlob; 
     public UInt32 Persist; 
     public UInt32 AttributeCount; 
     public IntPtr Attributes; 
     public IntPtr TargetAlias; 
     public IntPtr UserName; 

     internal static NativeCredential GetNativeCredential(Credential cred) 
     { 
      var ncred = new NativeCredential 
          { 
           AttributeCount = 0, 
           Attributes = IntPtr.Zero, 
           Comment = IntPtr.Zero, 
           TargetAlias = IntPtr.Zero, 
           Type = CRED_TYPE.DOMAIN_PASSWORD, 
           Persist = (UInt32) cred.Persist, 
           CredentialBlobSize = (UInt32) cred.CredentialBlobSize, 
           TargetName = Marshal.StringToCoTaskMemUni(cred.TargetName), 
           CredentialBlob = Marshal.StringToCoTaskMemUni(cred.CredentialBlob), 
           UserName = Marshal.StringToCoTaskMemUni(cred.UserName) 
          }; 
      return ncred; 
     } 
    } 

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
    public struct Credential 
    { 
     public UInt32 Flags; 
     public CRED_TYPE Type; 
     public string TargetName; 
     public string Comment; 
     public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; 
     public UInt32 CredentialBlobSize; 
     public string CredentialBlob; 
     public CRED_PERSIST Persist; 
     public UInt32 AttributeCount; 
     public IntPtr Attributes; 
     public string TargetAlias; 
     public string UserName; 
    } 

.. ...

 public static int WriteCred(string key, string userName, string secret, CRED_TYPE type, CRED_PERSIST credPersist) 
    { 

     var byteArray = Encoding.Unicode.GetBytes(secret); 
     if (byteArray.Length > 512) 
      throw new ArgumentOutOfRangeException("The secret message has exceeded 512 bytes."); 

     var cred = new Credential 
         { 
          TargetName = key, 
          CredentialBlob = secret, 
          CredentialBlobSize = (UInt32) Encoding.Unicode.GetBytes(secret).Length, 
          AttributeCount = 0, 
          Attributes = IntPtr.Zero, 
          UserName = userName, 
          Comment = null, 
          TargetAlias = null, 
          Type = type, 
          Persist = credPersist 
         }; 
     var ncred = NativeCredential.GetNativeCredential(cred); 

     var written = CredWrite(ref ncred, 0); 
     var lastError = Marshal.GetLastWin32Error(); 
     if (written) 
     { 
      return 0; 
     } 
     var message = ""; 
     if (lastError == 1312) 
     { 
      message = (string.Format("Failed to save " + key + " with error code {0}.", lastError) + " This error typically occurrs on home editions of Windows XP and Vista. Verify the version of Windows is Pro/Business or higher."); 
     } 
     else 
     { 
      message = string.Format("Failed to save " + key + " with error code {0}.", lastError); 
     } 
     MessageBox.Show(message); 
     return 1; 
    } 

Kann irgendjemand in die richtige Richtung zeigen, wie ich diese Arten von Anmeldeinformationen erhalten kann?

Dank

+1

Sie sollten einen umgekehrten Schrägstrich auf alten Betriebssystemen verwenden. –

Antwort

1

Unerklärbar. Ich habe das Problem gefunden. Nach der Überprüfung meiner Frage stellte ich fest, dass das hier Teil Credential Zähler verwaltet:

internal static NativeCredential GetNativeCredential(Credential cred) 
    { 
     var ncred = new NativeCredential 
         { 
          AttributeCount = 0, 
          Attributes = IntPtr.Zero, 
          Comment = IntPtr.Zero, 
          TargetAlias = IntPtr.Zero, 
          Type = CRED_TYPE.DOMAIN_PASSWORD, 
          Persist = (UInt32) cred.Persist, 
          CredentialBlobSize = (UInt32) cred.CredentialBlobSize, 
          TargetName = Marshal.StringToCoTaskMemUni(cred.TargetName), 
          CredentialBlob = Marshal.StringToCoTaskMemUni(cred.CredentialBlob), 
          UserName = Marshal.StringToCoTaskMemUni(cred.UserName) 
         }; 
     return ncred; 
    } 

das TYPE statt den Typ nur CRED.TYPE.DOMAIN_PASSWORD hatte vorgegeben decalring.

Wechsel zu:

internal static NativeCredential GetNativeCredential(Credential cred) 
    { 
     var ncred = new NativeCredential 
         { 
          AttributeCount = 0, 
          Attributes = IntPtr.Zero, 
          Comment = IntPtr.Zero, 
          TargetAlias = IntPtr.Zero, 
          Type = cred.type, 
          Persist = (UInt32) cred.Persist, 
          CredentialBlobSize = (UInt32) cred.CredentialBlobSize, 
          TargetName = Marshal.StringToCoTaskMemUni(cred.TargetName), 
          CredentialBlob = Marshal.StringToCoTaskMemUni(cred.CredentialBlob), 
          UserName = Marshal.StringToCoTaskMemUni(cred.UserName) 
         }; 
     return ncred; 
    } 

und das Senden der Anmeldeinformationen als ein GENERIC das Problem behoben. Anscheinend erlaubte Windows Vista/7 das "/" auf einem DOMAIN_PASSWORD- und GENERIC-Persistenzpasswort, aber XP erlaubt dies nur für GENERIC.

Verwandte Themen