1

現在、 Advapi32.dll から CredRead/CredWriteを呼び出す C# で記述されたプログラムを使用して、ドメインに参加していないユーザーがターミナル サーバー/WebDAV サーバーの資格情報を簡単に保存できるようにしています。(資格情報の構造については、http://msdn.microsoft.com/en-us/library/aa374788%28v=vs.85%29.aspx )

これはすべて正常に動作していますが、先日、すべての XP オペレーティング システムで持続する問題に遭遇しました。ターミナル サーバーの資格情報を保存できません。ネイティブの CredWrite メソッドがエラー コード 87 (ERROR_INVALID_PARAMETER) を返しています。

クレデンシャルの永続性とタイプのさまざまな組み合わせを数多く試した結果、問題は TargetName 自体にあることに気付きました。ターミナル サーバーを Windows キー リングに保存する場合、TargetName は TERMSRV/server.domain.com です。Windows Vista 以降では、これは私のコードで正常に動作しますが、Windows XP では動作しません。

奇妙なことに、Windows XP でリモート デスクトップ アプリケーションを実行して資格情報を保存すると、問題なく保存されます。RDP の使用後に保存された資格情報を列挙するメソッドを作成したとき (明らかに、パスワード blob は返されませんでした)、ターゲットが私のプログラムが書き込もうとしたものと同一であることがわかりました。

問題が TargetName にあることを確認するために、「/」を削除したところ、問題なく動作しました。

以下は私のコードの関連部分です:

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;
    }

これらのタイプの資格情報を保存する方法について、正しい方向に向けることはできますか?

ありがとう

4

1 に答える 1

1

どうでも。問題が見つかりました。私の質問を確認した後、管理された資格情報のカウンターパートがここにあることに気付きました:

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;
    }

タイプをデカルするだけでなく、CRED.TYPE.DOMAIN_PASSWORDに事前定義されたTYPEを持っていました。

変更:

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;
    }

クレデンシャルをGENERICとして送信すると、問題が解決しました。どうやら、Windows Vista/7 では DOMAIN_PASSWORD および GENERIC 永続パスワードで「/」を許可していましたが、XP では GENERIC に対してのみ許可されています。

于 2012-04-06T13:42:32.777 に答える