DSCResources/Grani_CredentialManager/CredentialManager.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Management.Automation;
using System.Runtime.InteropServices;

// ReSharper disable once CheckNamespace
namespace GraniResource
{
    public enum CredType : uint
    {
        Generic = 1,
        DomainPassword = 2,
        DomainCertificate = 3,
        DomainVisiblePassword = 4,
        GenericCertificate = 5,
        DomainExtended = 6,
        Maximum = 7,  // Maximum supported cred Type
        MaximumEx = (Maximum + 1000),  // Allow new applications to run on old OSes
    }

    public class CredentialRecord
    {
        public string Target { get; private set; }
        public string UserName { get; private set; }
        public DateTime LastWritten { get; private set; }
        public CredType Type { get; private set; }

        public CredentialRecord() { }

        public CredentialRecord(string userName, string target, long lastWritten, CredType type)
        {
            UserName = userName;
            Target = target;
            LastWritten = DateTime.FromFileTimeUtc(lastWritten);
            Type = type;
        }
    }

    public class CredentialManager
    {
        public static void Write(string target, PSCredential credential, CredType type)
        {
            if (credential == null) throw new NullReferenceException("Credential");

            var user = credential.GetNetworkCredential().UserName;
            var pass = credential.GetNetworkCredential().Password;
            var domain = credential.GetNetworkCredential().Domain;
            if (!string.IsNullOrWhiteSpace(domain))
            {
                user = string.Format(@"{0}\{1}", domain, user);
            }
            var nativeCredential = new NativeMethod.NativeWriteCredential
            {
                Flags = 0,
                Type = type,
                TargetName = Marshal.StringToCoTaskMemUni(target),
                UserName = Marshal.StringToCoTaskMemUni(user),
                AttributeCount = 0,
                Persist = 2,
                CredentialBlobSize = (uint)System.Text.Encoding.Unicode.GetByteCount(pass),
                CredentialBlob = Marshal.StringToCoTaskMemUni(pass),
            };
            if (!NativeMethod.CredWrite(ref nativeCredential, 0))
            {
                var errorCode = Marshal.GetLastWin32Error();
                throw new Exception("Failed to write credentials", new Win32Exception(errorCode));
            }
        }

        public static PSCredential Read(string target, CredType type, string userName)
        {
            IntPtr credentialPtr;
            if (!NativeMethod.CredRead(target, type, 0, out credentialPtr))
            {
                throw new NullReferenceException("Failed to find credentials in Windows Credential Manager. TargetName: {0}, Type {1}");
            }

            using (var handler = new NativeMethod.CriticalCredentialHandle(credentialPtr))
            {
                var result = GetNetworkCredential(handler, userName);
                return result;
            }
        }

        public static CredentialRecord[] List(CredType type = CredType.Generic)
        {
            var result = NativeMethod.NativeReadCredential.EnumerateCredential()
                .Where(x => x.Type == type)
                .Select(x => new CredentialRecord(x.UserName, x.TargetName, x.LastWritten, x.Type))
                .ToArray();
            return result;
        }

        public static bool Exists(string target, CredType type)
        {
            try
            {
                Read(target, type, "");
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public static void Remove(string target, CredType type)
        {
            if (!NativeMethod.CredDelete(target, type, 0))
            {
                throw new NullReferenceException(string.Format("Failed to find credentials in Windows Credential Manager. TargetName: {0}, Type {1}", target, type));
            }
        }

        private static PSCredential GetNetworkCredential(NativeMethod.CriticalCredentialHandle handler, string userName)
        {
            var credential = handler.GetCredential();
            if (string.IsNullOrWhiteSpace(userName))
            {
                userName = credential.UserName;
            }
            var secureString = new System.Security.SecureString();
            foreach (var c in credential.CredentialBlob)
                secureString.AppendChar(c);
            var psCredential = new PSCredential(userName, secureString);
            return psCredential;
        }
    }

    internal class NativeMethod
    {
        [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredWriteW", CharSet = CharSet.Unicode)]
        internal static extern bool CredWrite([In] ref NativeWriteCredential userWriteCredential, [In] uint flags);

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

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

        [DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern bool CredDelete(string target, CredType type, int reservedFlag);

        [DllImport("Advapi32.dll", EntryPoint = "CredEnumerate", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr pCredentials);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct NativeReadCredential
        {
            public uint Flags;
            public CredType Type;
            public string TargetName;
            public string Comment;
            public long LastWritten;
            public uint CredentialBlobSize;
            public string CredentialBlob;
            public uint Persist;
            public uint AttributeCount;
            public IntPtr Attributes;
            public string TargetAlias;
            public string UserName;

            public static IEnumerable<NativeReadCredential> EnumerateCredential()
            {
                int count;
                IntPtr pCredentials;
                var ret = CredEnumerate(null, 0, out count, out pCredentials);

                if (ret == false)
                    throw new Exception("Failed to enumerate credentials");

                var credentials = new IntPtr[count];
                for (var n = 0; n < count; n++)
                {
                    credentials[n] = Marshal.ReadIntPtr(pCredentials,
                        n * Marshal.SizeOf(typeof(IntPtr)));
                }

                return credentials.Select(ptr => (NativeReadCredential)Marshal.PtrToStructure(ptr, typeof(NativeReadCredential)));
            }
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct NativeWriteCredential
        {
            public uint Flags;
            public CredType Type;
            public IntPtr TargetName;
            public IntPtr Comment;
            public long LastWritten;
            public uint CredentialBlobSize;
            public IntPtr CredentialBlob;
            public uint Persist;
            public uint AttributeCount;
            public IntPtr Attributes;
            public IntPtr TargetAlias;
            public IntPtr UserName;
        }

        internal class CriticalCredentialHandle : Microsoft.Win32.SafeHandles.CriticalHandleZeroOrMinusOneIsInvalid
        {
            internal CriticalCredentialHandle(IntPtr preexistingHandle)
            {
                SetHandle(preexistingHandle);
            }

            internal NativeReadCredential GetCredential()
            {
                if (IsInvalid) throw new InvalidOperationException("Invalid CriticalHandle!");
                var ncred = (NativeWriteCredential)Marshal.PtrToStructure(handle, typeof(NativeWriteCredential));
                var cred = new NativeReadCredential
                {
                    CredentialBlobSize = ncred.CredentialBlobSize,
                    CredentialBlob = Marshal.PtrToStringUni(ncred.CredentialBlob, (int)ncred.CredentialBlobSize / 2),
                    UserName = Marshal.PtrToStringUni(ncred.UserName),
                    TargetName = Marshal.PtrToStringUni(ncred.TargetName),
                    TargetAlias = Marshal.PtrToStringUni(ncred.TargetAlias),
                    Type = ncred.Type,
                    Flags = ncred.Flags,
                    Persist = ncred.Persist
                };
                return cred;
            }

            protected override bool ReleaseHandle()
            {
                if (IsInvalid) return false;
                CredFree(handle);
                SetHandleAsInvalid();
                return true;
            }
        }
    }
}