1 #!powershell 2 3 # Copyright: (c) 2018, Ansible Project 4 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6 #AnsibleRequires -CSharpUtil Ansible.Basic 7 #Requires -Module Ansible.ModuleUtils.AddType 8 9 $spec = @{ 10 options = @{ 11 name = @{ type = "str"; required = $true } 12 type = @{ type = "str"; required = $true; choices = @("domain_password", "domain_certificate", "generic_password", "generic_certificate") } 13 } 14 supports_check_mode = $true 15 } 16 17 $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) 18 19 $name = $module.Params.name 20 $type = $module.Params.type 21 22 Add-CSharpType -AnsibleModule $module -References @' 23 using Microsoft.Win32.SafeHandles; 24 using System; 25 using System.Collections.Generic; 26 using System.Linq; 27 using System.Runtime.ConstrainedExecution; 28 using System.Runtime.InteropServices; 29 using System.Text; 30 31 namespace Ansible.CredentialManager 32 { 33 internal class NativeHelpers 34 { 35 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 36 public class CREDENTIAL 37 { 38 public CredentialFlags Flags; 39 public CredentialType Type; 40 [MarshalAs(UnmanagedType.LPWStr)] public string TargetName; 41 [MarshalAs(UnmanagedType.LPWStr)] public string Comment; 42 public FILETIME LastWritten; 43 public UInt32 CredentialBlobSize; 44 public IntPtr CredentialBlob; 45 public CredentialPersist Persist; 46 public UInt32 AttributeCount; 47 public IntPtr Attributes; 48 [MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias; 49 [MarshalAs(UnmanagedType.LPWStr)] public string UserName; 50 51 public static explicit operator Credential(CREDENTIAL v) 52 { 53 byte[] secret = new byte[(int)v.CredentialBlobSize]; 54 if (v.CredentialBlob != IntPtr.Zero) 55 Marshal.Copy(v.CredentialBlob, secret, 0, secret.Length); 56 57 List<CredentialAttribute> attributes = new List<CredentialAttribute>(); 58 if (v.AttributeCount > 0) 59 { 60 CREDENTIAL_ATTRIBUTE[] rawAttributes = new CREDENTIAL_ATTRIBUTE[v.AttributeCount]; 61 Credential.PtrToStructureArray(rawAttributes, v.Attributes); 62 attributes = rawAttributes.Select(x => (CredentialAttribute)x).ToList(); 63 } 64 65 string userName = v.UserName; 66 if (v.Type == CredentialType.DomainCertificate || v.Type == CredentialType.GenericCertificate) 67 userName = Credential.UnmarshalCertificateCredential(userName); 68 69 return new Credential 70 { 71 Type = v.Type, 72 TargetName = v.TargetName, 73 Comment = v.Comment, 74 LastWritten = (DateTimeOffset)v.LastWritten, 75 Secret = secret, 76 Persist = v.Persist, 77 Attributes = attributes, 78 TargetAlias = v.TargetAlias, 79 UserName = userName, 80 Loaded = true, 81 }; 82 } 83 } 84 85 [StructLayout(LayoutKind.Sequential)] 86 public struct CREDENTIAL_ATTRIBUTE 87 { 88 [MarshalAs(UnmanagedType.LPWStr)] public string Keyword; 89 public UInt32 Flags; // Set to 0 and is reserved 90 public UInt32 ValueSize; 91 public IntPtr Value; 92 93 public static explicit operator CredentialAttribute(CREDENTIAL_ATTRIBUTE v) 94 { 95 byte[] value = new byte[v.ValueSize]; 96 Marshal.Copy(v.Value, value, 0, (int)v.ValueSize); 97 98 return new CredentialAttribute 99 { 100 Keyword = v.Keyword, 101 Flags = v.Flags, 102 Value = value, 103 }; 104 } 105 } 106 107 [StructLayout(LayoutKind.Sequential)] 108 public struct FILETIME 109 { 110 internal UInt32 dwLowDateTime; 111 internal UInt32 dwHighDateTime; 112 113 public static implicit operator long(FILETIME v) { return ((long)v.dwHighDateTime << 32) + v.dwLowDateTime; } 114 public static explicit operator DateTimeOffset(FILETIME v) { return DateTimeOffset.FromFileTime(v); } 115 public static explicit operator FILETIME(DateTimeOffset v) 116 { 117 return new FILETIME() 118 { 119 dwLowDateTime = (UInt32)v.ToFileTime(), 120 dwHighDateTime = ((UInt32)v.ToFileTime() >> 32), 121 }; 122 } 123 } 124 125 [Flags] 126 public enum CredentialCreateFlags : uint 127 { 128 PreserveCredentialBlob = 1, 129 } 130 131 [Flags] 132 public enum CredentialFlags 133 { 134 None = 0, 135 PromptNow = 2, 136 UsernameTarget = 4, 137 } 138 139 public enum CredMarshalType : uint 140 { 141 CertCredential = 1, 142 UsernameTargetCredential, 143 BinaryBlobCredential, 144 UsernameForPackedCredential, 145 BinaryBlobForSystem, 146 } 147 } 148 149 internal class NativeMethods 150 { 151 [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 152 public static extern bool CredDeleteW( 153 [MarshalAs(UnmanagedType.LPWStr)] string TargetName, 154 CredentialType Type, 155 UInt32 Flags); 156 157 [DllImport("advapi32.dll")] 158 public static extern void CredFree( 159 IntPtr Buffer); 160 161 [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 162 public static extern bool CredMarshalCredentialW( 163 NativeHelpers.CredMarshalType CredType, 164 SafeMemoryBuffer Credential, 165 out SafeCredentialBuffer MarshaledCredential); 166 167 [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 168 public static extern bool CredReadW( 169 [MarshalAs(UnmanagedType.LPWStr)] string TargetName, 170 CredentialType Type, 171 UInt32 Flags, 172 out SafeCredentialBuffer Credential); 173 174 [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 175 public static extern bool CredUnmarshalCredentialW( 176 [MarshalAs(UnmanagedType.LPWStr)] string MarshaledCredential, 177 out NativeHelpers.CredMarshalType CredType, 178 out SafeCredentialBuffer Credential); 179 180 [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 181 public static extern bool CredWriteW( 182 NativeHelpers.CREDENTIAL Credential, 183 NativeHelpers.CredentialCreateFlags Flags); 184 } 185 186 internal class SafeCredentialBuffer : SafeHandleZeroOrMinusOneIsInvalid 187 { 188 public SafeCredentialBuffer() : base(true) { } 189 190 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] 191 protected override bool ReleaseHandle() 192 { 193 NativeMethods.CredFree(handle); 194 return true; 195 } 196 } 197 198 internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid 199 { 200 public SafeMemoryBuffer() : base(true) { } 201 public SafeMemoryBuffer(int cb) : base(true) 202 { 203 base.SetHandle(Marshal.AllocHGlobal(cb)); 204 } 205 public SafeMemoryBuffer(IntPtr handle) : base(true) 206 { 207 base.SetHandle(handle); 208 } 209 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] 210 protected override bool ReleaseHandle() 211 { 212 Marshal.FreeHGlobal(handle); 213 return true; 214 } 215 } 216 217 public class Win32Exception : System.ComponentModel.Win32Exception 218 { 219 private string _exception_msg; 220 public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { } 221 public Win32Exception(int errorCode, string message) : base(errorCode) 222 { 223 _exception_msg = String.Format("{0} - {1} (Win32 Error Code {2}: 0x{3})", message, base.Message, errorCode, errorCode.ToString("X8")); 224 } 225 public override string Message { get { return _exception_msg; } } 226 public static explicit operator Win32Exception(string message) { return new Win32Exception(message); } 227 } 228 229 public enum CredentialPersist 230 { 231 Session = 1, 232 LocalMachine = 2, 233 Enterprise = 3, 234 } 235 236 public enum CredentialType 237 { 238 Generic = 1, 239 DomainPassword = 2, 240 DomainCertificate = 3, 241 DomainVisiblePassword = 4, 242 GenericCertificate = 5, 243 DomainExtended = 6, 244 Maximum = 7, 245 MaximumEx = 1007, 246 } 247 248 public class CredentialAttribute 249 { 250 public string Keyword; 251 public UInt32 Flags; 252 public byte[] Value; 253 } 254 255 public class Credential 256 { 257 public CredentialType Type; 258 public string TargetName; 259 public string Comment; 260 public DateTimeOffset LastWritten; 261 public byte[] Secret; 262 public CredentialPersist Persist; 263 public List<CredentialAttribute> Attributes = new List<CredentialAttribute>(); 264 public string TargetAlias; 265 public string UserName; 266 267 // Used to track whether the credential has been loaded into the store or not 268 public bool Loaded { get; internal set; } 269 270 public void Delete() 271 { 272 if (!Loaded) 273 return; 274 275 if (!NativeMethods.CredDeleteW(TargetName, Type, 0)) 276 throw new Win32Exception(String.Format("CredDeleteW({0}) failed", TargetName)); 277 Loaded = false; 278 } 279 280 public void Write(bool preserveExisting) 281 { 282 string userName = UserName; 283 // Convert the certificate thumbprint to the string expected 284 if (Type == CredentialType.DomainCertificate || Type == CredentialType.GenericCertificate) 285 userName = Credential.MarshalCertificateCredential(userName); 286 287 NativeHelpers.CREDENTIAL credential = new NativeHelpers.CREDENTIAL 288 { 289 Flags = NativeHelpers.CredentialFlags.None, 290 Type = Type, 291 TargetName = TargetName, 292 Comment = Comment, 293 LastWritten = new NativeHelpers.FILETIME(), 294 CredentialBlobSize = (UInt32)(Secret == null ? 0 : Secret.Length), 295 CredentialBlob = IntPtr.Zero, // Must be allocated and freed outside of this to ensure no memory leaks 296 Persist = Persist, 297 AttributeCount = (UInt32)(Attributes.Count), 298 Attributes = IntPtr.Zero, // Attributes must be allocated and freed outside of this to ensure no memory leaks 299 TargetAlias = TargetAlias, 300 UserName = userName, 301 }; 302 303 using (SafeMemoryBuffer credentialBlob = new SafeMemoryBuffer((int)credential.CredentialBlobSize)) 304 { 305 if (Secret != null) 306 Marshal.Copy(Secret, 0, credentialBlob.DangerousGetHandle(), Secret.Length); 307 credential.CredentialBlob = credentialBlob.DangerousGetHandle(); 308 309 // Store the CREDENTIAL_ATTRIBUTE value in a safe memory buffer and make sure we dispose in all cases 310 List<SafeMemoryBuffer> attributeBuffers = new List<SafeMemoryBuffer>(); 311 try 312 { 313 int attributeLength = Attributes.Sum(a => Marshal.SizeOf(typeof(NativeHelpers.CREDENTIAL_ATTRIBUTE))); 314 byte[] attributeBytes = new byte[attributeLength]; 315 int offset = 0; 316 foreach (CredentialAttribute attribute in Attributes) 317 { 318 SafeMemoryBuffer attributeBuffer = new SafeMemoryBuffer(attribute.Value.Length); 319 attributeBuffers.Add(attributeBuffer); 320 if (attribute.Value != null) 321 Marshal.Copy(attribute.Value, 0, attributeBuffer.DangerousGetHandle(), attribute.Value.Length); 322 323 NativeHelpers.CREDENTIAL_ATTRIBUTE credentialAttribute = new NativeHelpers.CREDENTIAL_ATTRIBUTE 324 { 325 Keyword = attribute.Keyword, 326 Flags = attribute.Flags, 327 ValueSize = (UInt32)(attribute.Value == null ? 0 : attribute.Value.Length), 328 Value = attributeBuffer.DangerousGetHandle(), 329 }; 330 int attributeStructLength = Marshal.SizeOf(typeof(NativeHelpers.CREDENTIAL_ATTRIBUTE)); 331 332 byte[] attrBytes = new byte[attributeStructLength]; 333 using (SafeMemoryBuffer tempBuffer = new SafeMemoryBuffer(attributeStructLength)) 334 { 335 Marshal.StructureToPtr(credentialAttribute, tempBuffer.DangerousGetHandle(), false); 336 Marshal.Copy(tempBuffer.DangerousGetHandle(), attrBytes, 0, attributeStructLength); 337 } 338 Buffer.BlockCopy(attrBytes, 0, attributeBytes, offset, attributeStructLength); 339 offset += attributeStructLength; 340 } 341 342 using (SafeMemoryBuffer attributes = new SafeMemoryBuffer(attributeBytes.Length)) 343 { 344 if (attributeBytes.Length != 0) 345 Marshal.Copy(attributeBytes, 0, attributes.DangerousGetHandle(), attributeBytes.Length); 346 credential.Attributes = attributes.DangerousGetHandle(); 347 348 NativeHelpers.CredentialCreateFlags createFlags = 0; 349 if (preserveExisting) 350 createFlags |= NativeHelpers.CredentialCreateFlags.PreserveCredentialBlob; 351 352 if (!NativeMethods.CredWriteW(credential, createFlags)) 353 throw new Win32Exception(String.Format("CredWriteW({0}) failed", TargetName)); 354 } 355 } 356 finally 357 { 358 foreach (SafeMemoryBuffer attributeBuffer in attributeBuffers) 359 attributeBuffer.Dispose(); 360 } 361 } 362 Loaded = true; 363 } 364 365 public static Credential GetCredential(string target, CredentialType type) 366 { 367 SafeCredentialBuffer buffer; 368 if (!NativeMethods.CredReadW(target, type, 0, out buffer)) 369 { 370 int lastErr = Marshal.GetLastWin32Error(); 371 372 // Not running with CredSSP or Become so cannot manage the user's credentials 373 if (lastErr == 0x00000520) // ERROR_NO_SUCH_LOGON_SESSION 374 throw new InvalidOperationException("Failed to access the user's credential store, run the module with become or CredSSP"); 375 else if (lastErr == 0x00000490) // ERROR_NOT_FOUND 376 return null; 377 throw new Win32Exception(lastErr, "CredEnumerateW() failed"); 378 } 379 380 using (buffer) 381 { 382 NativeHelpers.CREDENTIAL credential = (NativeHelpers.CREDENTIAL)Marshal.PtrToStructure( 383 buffer.DangerousGetHandle(), typeof(NativeHelpers.CREDENTIAL)); 384 return (Credential)credential; 385 } 386 } 387 388 public static string MarshalCertificateCredential(string thumbprint) 389 { 390 // CredWriteW requires the UserName field to be the value of CredMarshalCredentialW() when writting a 391 // certificate auth. This converts the UserName property to the format required. 392 393 // While CERT_CREDENTIAL_INFO is the correct structure, we manually marshal the data in order to 394 // support different cert hash lengths in the future. 395 // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_cert_credential_info 396 int hexLength = thumbprint.Length; 397 byte[] credInfo = new byte[sizeof(UInt32) + (hexLength / 2)]; 398 399 // First field is cbSize which is a UInt32 value denoting the size of the total structure 400 Array.Copy(BitConverter.GetBytes((UInt32)credInfo.Length), credInfo, sizeof(UInt32)); 401 402 // Now copy the byte representation of the thumbprint to the rest of the struct bytes 403 for (int i = 0; i < hexLength; i += 2) 404 credInfo[sizeof(UInt32) + (i / 2)] = Convert.ToByte(thumbprint.Substring(i, 2), 16); 405 406 IntPtr pCredInfo = Marshal.AllocHGlobal(credInfo.Length); 407 Marshal.Copy(credInfo, 0, pCredInfo, credInfo.Length); 408 SafeMemoryBuffer pCredential = new SafeMemoryBuffer(pCredInfo); 409 410 NativeHelpers.CredMarshalType marshalType = NativeHelpers.CredMarshalType.CertCredential; 411 using (pCredential) 412 { 413 SafeCredentialBuffer marshaledCredential; 414 if (!NativeMethods.CredMarshalCredentialW(marshalType, pCredential, out marshaledCredential)) 415 throw new Win32Exception("CredMarshalCredentialW() failed"); 416 using (marshaledCredential) 417 return Marshal.PtrToStringUni(marshaledCredential.DangerousGetHandle()); 418 } 419 } 420 421 public static string UnmarshalCertificateCredential(string value) 422 { 423 NativeHelpers.CredMarshalType credType; 424 SafeCredentialBuffer pCredInfo; 425 if (!NativeMethods.CredUnmarshalCredentialW(value, out credType, out pCredInfo)) 426 throw new Win32Exception("CredUnmarshalCredentialW() failed"); 427 428 using (pCredInfo) 429 { 430 if (credType != NativeHelpers.CredMarshalType.CertCredential) 431 throw new InvalidOperationException(String.Format("Expected unmarshalled cred type of CertCredential, received {0}", credType)); 432 433 byte[] structSizeBytes = new byte[sizeof(UInt32)]; 434 Marshal.Copy(pCredInfo.DangerousGetHandle(), structSizeBytes, 0, sizeof(UInt32)); 435 UInt32 structSize = BitConverter.ToUInt32(structSizeBytes, 0); 436 437 byte[] certInfoBytes = new byte[structSize]; 438 Marshal.Copy(pCredInfo.DangerousGetHandle(), certInfoBytes, 0, certInfoBytes.Length); 439 440 StringBuilder hex = new StringBuilder((certInfoBytes.Length - sizeof(UInt32)) * 2); 441 for (int i = 4; i < certInfoBytes.Length; i++) 442 hex.AppendFormat("{0:x2}", certInfoBytes[i]); 443 444 return hex.ToString().ToUpperInvariant(); 445 } 446 } 447 448 internal static void PtrToStructureArray<T>(T[] array, IntPtr ptr) 449 { 450 IntPtr ptrOffset = ptr; 451 for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T)))) 452 array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T)); 453 } 454 } 455 } 456 '@ 457 458 $type = switch ($type) { 459 "domain_password" { [Ansible.CredentialManager.CredentialType]::DomainPassword } 460 "domain_certificate" { [Ansible.CredentialManager.CredentialType]::DomainCertificate } 461 "generic_password" { [Ansible.CredentialManager.CredentialType]::Generic } 462 "generic_certificate" { [Ansible.CredentialManager.CredentialType]::GenericCertificate } 463 } 464 465 $credential = [Ansible.CredentialManager.Credential]::GetCredential($name, $type) 466 if ($null -ne $credential) { 467 $module.Result.exists = $true 468 $module.Result.alias = $credential.TargetAlias 469 $module.Result.attributes = [System.Collections.ArrayList]@() 470 $module.Result.comment = $credential.Comment 471 $module.Result.name = $credential.TargetName 472 $module.Result.persistence = $credential.Persist.ToString() 473 $module.Result.type = $credential.Type.ToString() 474 $module.Result.username = $credential.UserName 475 476 if ($null -ne $credential.Secret) { 477 $module.Result.secret = [System.Convert]::ToBase64String($credential.Secret) 478 } else { 479 $module.Result.secret = $null 480 } 481 482 foreach ($attribute in $credential.Attributes) { 483 $attribute_info = @{ 484 name = $attribute.Keyword 485 } 486 if ($null -ne $attribute.Value) { 487 $attribute_info.data = [System.Convert]::ToBase64String($attribute.Value) 488 } else { 489 $attribute_info.data = $null 490 } 491 $module.Result.attributes.Add($attribute_info) > $null 492 } 493 } else { 494 $module.Result.exists = $false 495 } 496 497 $module.ExitJson() 498 499