もうちょっと複雑なことができるものを作りました。
[2014/07/03追記ここまで]
前書き
ちょっと訳あって資格情報マネージャを確認しようとしたらコントロールパネルから見れませんでした。多分、権限とかなんかそんなんがあって、非表示になってるんでしょう。機能はしているのでまったく見れないというわけではなさそうです。
別にそれでも困らないんだけど、ちょっとテストしたいこともあるので、とりあえず今登録されている資格情報が見たいなー、と思い、ちまちまとコードを書きました。
コード
using System;
using System.Runtime.InteropServices;
namespace ManagedCred
{
class Program
{
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr pCredentials);
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_EX = (MAXIMUM + 1000),
}
public enum CRED_PERSIST : uint
{
SESSION = 1,
LOCAL_MACHINE = 2,
ENTERPRISE = 3,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CREDENTIAL_ATTRIBUTE
{
string Keyword;
uint Flags;
uint ValueSize;
IntPtr Value;
}
/// <summary>
/// アンマネージドなCredential
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct Credential
{
public uint Flags;
public uint Type;
public string TargetName;
public string Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public uint CredentialBlobSize;
public IntPtr CredentialBlob;
public uint Persist;
public uint AttributeCount;
public IntPtr Attributes;
public string TargetAlias;
public string UserName;
}
/// <summary>
/// マネージコードに置き換えたCredential
/// </summary>
public class ManagedCredential
{
public uint Flags { get; set; }
public CRED_TYPE Type { get; set; }
public string TargetName { get; set; }
public string Comment { get; set; }
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten { get; set; }
public byte[] CredentialBlob { get; set; }
public CRED_PERSIST Persist { get; set; }
public CREDENTIAL_ATTRIBUTE[] Attributes { get; set; }
public string TargetAlias { get; set; }
public string UserName { get; set; }
}
public static void Main(string[] args)
{
int count = 0;
IntPtr pCredentials = IntPtr.Zero;
IntPtr[] credentials = null;
//CredEnumerate呼び出し
bool ret = CredEnumerate(null, 0, out count, out pCredentials);
if (ret != false)
{
credentials = new IntPtr[count];
for (int n = 0; n < count; n++)
{
credentials[n] = Marshal.ReadIntPtr(pCredentials, n * Marshal.SizeOf(typeof(IntPtr)));
}
}
foreach (var ptr in credentials)
{
//CredEnumerateで取得したIntPtrを構造体に変換する
var unCred = (Credential)Marshal.PtrToStructure(ptr, typeof(Credential));
var cred = new ManagedCredential
{
Flags = unCred.Flags,
Type = (CRED_TYPE)unCred.Type,
TargetName = unCred.TargetName,
Comment = unCred.Comment,
LastWritten = unCred.LastWritten,
CredentialBlob = new byte[unCred.CredentialBlobSize],
Persist = (CRED_PERSIST)unCred.Persist,
Attributes = new CREDENTIAL_ATTRIBUTE[unCred.AttributeCount],
TargetAlias = unCred.TargetAlias,
UserName = unCred.UserName,
};
var attrPtr = unCred.Attributes;
for (int i = 0; i < unCred.AttributeCount; i++)
{
cred.Attributes[i] = (CREDENTIAL_ATTRIBUTE)Marshal.PtrToStructure(ptr, typeof(CREDENTIAL_ATTRIBUTE));
attrPtr = (IntPtr)((int)ptr + Marshal.SizeOf(typeof(CREDENTIAL_ATTRIBUTE)));
}
if (unCred.CredentialBlobSize != 0)
Marshal.Copy(unCred.CredentialBlob, cred.CredentialBlob, 0, (int)unCred.CredentialBlobSize);
Console.WriteLine("Flags:{0}", cred.Flags);
Console.WriteLine("Type:{0}", cred.Type);
Console.WriteLine("TargetName:{0}", cred.TargetName);
Console.WriteLine("Comment:{0}", cred.Comment);
Console.WriteLine("Persist:{0}", cred.Persist);
Console.WriteLine("TargetAlias:{0}", cred.TargetAlias);
Console.WriteLine("UserName:{0}", cred.UserName);
Console.WriteLine("* * * * * * * * * *");
}
Console.ReadKey();
}
}
}
補足
残念ながら資格情報を取得するにはWin32 APIをぶったたく以外にないので、とりあえずCredEnumerateのドキュメントを読んでみましょう。
C++のシグネチャですが、こんな感じになっているみたいです。
BOOL CredEnumerate( _In_ LPCTSTR Filter, _In_ DWORD Flags, _Out_ DWORD *Count, _Out_ PCREDENTIAL **Credentials );
どれに何を渡せばいいのかはここを読んでもらうとして、面倒なのは「_Out_ PCREDENTIAL **Credentials」です。構造体の配列がポインタで返って来ます。ぴえー。
PCREDENTIALはこうなってます。
typedef struct _CREDENTIAL {
DWORD Flags;
DWORD Type;
LPTSTR TargetName;
LPTSTR Comment;
FILETIME LastWritten;
DWORD CredentialBlobSize;
LPBYTE CredentialBlob;
DWORD Persist;
DWORD AttributeCount;
PCREDENTIAL_ATTRIBUTE Attributes;
LPTSTR TargetAlias;
LPTSTR UserName;
}CREDENTIAL, *PCREDENTIAL;
TargetNameがサーバ名、UserNameが「ドメイン\ユーザ名」になっています。パスワードはCredentialBlobにゴニョゴニョされて入っているみたいですが、今回は特に必要ないので無視します。
逆に言えば、Windowsの資格情報なんてこれぐらい簡単に引っこ抜けるので、あんまり無防備に保存しないほうがいいですよ。
まとめ
資格情報マネージャは開けなくとも、このコードで普通に見れました。
でもよく考えたら資格情報消さないと目的のテストが出来ないので、その辺のロジックも実装しないといけません。もうアンマネージドなコードは触りたくないんだけど…。
参考
- Windows XP および Windows Server 2003 での資格情報管理の使用方法
- 資格情報マネージャをマネージドコードから使用する。 - 設計と実装の狭間で。
- Credential Management with the .NET Framework 2.0
- Credentials Management
- C# CredEnumerate and CredRead
- Unmanaged API returning an array of pointers - C# - C Sharp
- c# - Help with CredEnumerate - Stack Overflow
- pinvoke.net CredRead (advapi32)
- IntPtrから配列に変換 - Memo+