From b1a7f3fffaa9f1bd9604c8cc1e1b2f50df3dd11a Mon Sep 17 00:00:00 2001 From: Ady Date: Wed, 20 Jan 2016 20:51:02 +0530 Subject: [PATCH] Updated Windows Credential, added Console credential prompts --- .../AdysTech.CredentialManager.csproj | 2 +- AdysTech.CredentialManager/Credential.cs | 16 +- .../CredentialManager.cs | 162 ++++++++++++++---- .../CriticalCredentialHandle.cs | 6 +- AdysTech.CredentialManager/NativeStructs.cs | 120 ------------- .../CredentialManagerTest.cs | 76 +++++++- 6 files changed, 213 insertions(+), 169 deletions(-) delete mode 100644 AdysTech.CredentialManager/NativeStructs.cs diff --git a/AdysTech.CredentialManager/AdysTech.CredentialManager.csproj b/AdysTech.CredentialManager/AdysTech.CredentialManager.csproj index fa6f505..d99300c 100644 --- a/AdysTech.CredentialManager/AdysTech.CredentialManager.csproj +++ b/AdysTech.CredentialManager/AdysTech.CredentialManager.csproj @@ -41,7 +41,7 @@ - + diff --git a/AdysTech.CredentialManager/Credential.cs b/AdysTech.CredentialManager/Credential.cs index 89c2122..3632922 100644 --- a/AdysTech.CredentialManager/Credential.cs +++ b/AdysTech.CredentialManager/Credential.cs @@ -10,13 +10,13 @@ namespace AdysTech.CredentialManager internal class Credential { public UInt32 Flags; - public NativeStructs.CredentialType Type; + public NativeCode.CredentialType Type; public string TargetName; public string Comment; public DateTime LastWritten; public UInt32 CredentialBlobSize; public string CredentialBlob; - public NativeStructs.Persistance Persist; + public NativeCode.Persistance Persist; public UInt32 AttributeCount; public IntPtr Attributes; public string TargetAlias; @@ -27,7 +27,7 @@ public Credential() } - internal Credential(NativeStructs.NativeCredential ncred) + internal Credential(NativeCode.NativeCredential ncred) { CredentialBlobSize = ncred.CredentialBlobSize; CredentialBlob = Marshal.PtrToStringUni (ncred.CredentialBlob, @@ -37,7 +37,7 @@ internal Credential(NativeStructs.NativeCredential ncred) TargetAlias = Marshal.PtrToStringUni (ncred.TargetAlias); Type = ncred.Type; Flags = ncred.Flags; - Persist = (NativeStructs.Persistance) ncred.Persist; + Persist = (NativeCode.Persistance) ncred.Persist; LastWritten = DateTime.FromFileTime ((long) ( (ulong) ncred.LastWritten.dwHighDateTime << 32 | (ulong) ncred.LastWritten.dwLowDateTime )); } @@ -50,8 +50,8 @@ public Credential(System.Net.NetworkCredential credential) Attributes = IntPtr.Zero; Comment = null; TargetAlias = null; - Type = NativeStructs.CredentialType.GENERIC; - Persist = NativeStructs.Persistance.SESSION; + Type = NativeCode.CredentialType.Generic; + Persist = NativeCode.Persistance.Session; } /// @@ -60,9 +60,9 @@ public Credential(System.Net.NetworkCredential credential) /// The managed Credential counterpart containing data to be stored. /// A NativeCredential instance that is derived from the given Credential /// instance. - internal NativeStructs.NativeCredential GetNativeCredential() + internal NativeCode.NativeCredential GetNativeCredential() { - NativeStructs.NativeCredential ncred = new NativeStructs.NativeCredential (); + NativeCode.NativeCredential ncred = new NativeCode.NativeCredential (); ncred.AttributeCount = 0; ncred.Attributes = IntPtr.Zero; ncred.Comment = IntPtr.Zero; diff --git a/AdysTech.CredentialManager/CredentialManager.cs b/AdysTech.CredentialManager/CredentialManager.cs index 7989038..5635bb3 100644 --- a/AdysTech.CredentialManager/CredentialManager.cs +++ b/AdysTech.CredentialManager/CredentialManager.cs @@ -1,12 +1,9 @@ -using Microsoft.Win32.SafeHandles; -using System; -using System.Collections.Generic; +using System; using System.ComponentModel; -using System.Linq; +using System.Diagnostics; using System.Net; using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; namespace AdysTech.CredentialManager { @@ -20,14 +17,15 @@ public static class CredentialManager /// Opens OS Version specific Window prompting for credentials /// /// A descriptive text for where teh credentials being asked are used for + /// Whether or not to offer the checkbox to save the credentials /// NetworkCredential object containing the user name, - public static NetworkCredential PromptForCredentials(string Target) + public static NetworkCredential PromptForCredentials(string Target, ref bool save) { var username = String.Empty; var passwd = String.Empty; var domain = String.Empty; - if ( !PromptForCredentials (Target, out username, out passwd, out domain) ) + if ( !PromptForCredentials (Target, ref save, out username, out passwd, out domain) ) return null; return new NetworkCredential (username, passwd, domain); } @@ -36,73 +34,165 @@ public static NetworkCredential PromptForCredentials(string Target) /// Opens OS Version specific Window prompting for credentials /// /// A descriptive text for where teh credentials being asked are used for + /// Whether or not to offer the checkbox to save the credentials /// A brief message to display in the dialog box /// Title for the dialog box /// NetworkCredential object containing the user name, - public static NetworkCredential PromptForCredentials(string Target, string Message, string Caption) + public static NetworkCredential PromptForCredentials(string Target, ref bool save, string Message, string Caption) { var username = String.Empty; var passwd = String.Empty; var domain = String.Empty; - if ( !PromptForCredentials (Target, Message, Caption, out username, out passwd, out domain) ) + if ( !PromptForCredentials (Target, ref save, Message, Caption, out username, out passwd, out domain) ) return null; return new NetworkCredential (username, passwd, domain); } - internal static bool PromptForCredentials(string target, out string user, out string password, out string domain) + internal static bool PromptForCredentials(string target, ref bool save, out string user, out string password, out string domain) { - return PromptForCredentials (target, new NativeStructs.CredentialUIInfo (), out user, out password, out domain); + return PromptForCredentials (target, new NativeCode.CredentialUIInfo (), ref save, out user, out password, out domain); } - internal static bool PromptForCredentials(string target, string Message, string Caption, out string user, out string password, out string domain) + internal static bool PromptForCredentials(string target, ref bool save, string Message, string Caption, out string user, out string password, out string domain) { - NativeStructs.CredentialUIInfo credUI = new NativeStructs.CredentialUIInfo (); + NativeCode.CredentialUIInfo credUI = new NativeCode.CredentialUIInfo (); credUI.pszMessageText = Message; credUI.pszCaptionText = Caption; - return PromptForCredentials (target, credUI, out user, out password, out domain); + return PromptForCredentials (target, credUI, ref save, out user, out password, out domain); } - private static bool PromptForCredentials(string target, NativeStructs.CredentialUIInfo credUI, out string user, out string password, out string domain) + private static bool PromptForCredentials(string target, NativeCode.CredentialUIInfo credUI, ref bool save, out string user, out string password, out string domain) { + user = String.Empty; + password = String.Empty; + domain = String.Empty; + + + // Setup the flags and variables - StringBuilder userPassword = new StringBuilder (), userID = new StringBuilder (); credUI.cbSize = Marshal.SizeOf (credUI); + int errorcode = 0; + uint dialogReturn; + uint authPackage = 0; + + IntPtr outCredBuffer = new IntPtr (); + uint outCredSize; + var flags = NativeCode.PromptForWindowsCredentialsFlags.GenericCredentials | NativeCode.PromptForWindowsCredentialsFlags.EnumerateCurrentUser; + flags = save ? flags | NativeCode.PromptForWindowsCredentialsFlags.ShowCheckbox : flags; + + // Setup the flags and variables + int result = NativeCode.CredUIPromptForWindowsCredentials (ref credUI, + errorcode, + ref authPackage, + IntPtr.Zero, + 0, + out outCredBuffer, + out outCredSize, + ref save, + flags); + + var usernameBuf = new StringBuilder (100); + var passwordBuf = new StringBuilder (100); + var domainBuf = new StringBuilder (100); + + int maxUserName = 100; + int maxDomain = 100; + int maxPassword = 100; + if ( result == 0 ) + { + if ( NativeCode.CredUnPackAuthenticationBuffer (0, outCredBuffer, outCredSize, usernameBuf, ref maxUserName, + domainBuf, ref maxDomain, passwordBuf, ref maxPassword) ) + { + user = usernameBuf.ToString (); + password = passwordBuf.ToString (); + domain = domainBuf.ToString (); + if ( String.IsNullOrWhiteSpace (domain) ) + { + Debug.WriteLine ("Domain null"); + if ( !ParseUserName (usernameBuf.ToString (), maxUserName, maxDomain, out user, out password) ) + user = usernameBuf.ToString (); + } + } + + //mimic SecureZeroMem function to make sure buffer is zeroed out. SecureZeroMem is not an exported function, neither is RtlSecureZeroMemory + var zeroBytes = new byte[outCredSize]; + Marshal.Copy (zeroBytes, 0, outCredBuffer, (int) outCredSize); + + //clear the memory allocated by CredUIPromptForWindowsCredentials + NativeCode.CoTaskMemFree (outCredBuffer); + return true; + } + + user = null; + domain = null; + return false; + } + + private static bool ParseUserName(string usernameBuf, int maxUserName, int maxDomain, out string user, out string domain) + { + StringBuilder userBuilder = new StringBuilder (); + StringBuilder domainBuilder = new StringBuilder (); + user = String.Empty; + domain = String.Empty; + + var returnCode = NativeCode.CredUIParseUserName (usernameBuf, userBuilder, maxUserName, domainBuilder, maxDomain); + Debug.WriteLine (returnCode); + switch ( returnCode ) + { + case NativeCode.CredentialUIReturnCodes.Success: // The username is valid. + user = userBuilder.ToString (); + domain = domainBuilder.ToString (); + return true; + } + return false; + } + + /// + /// Accepts credentials in a console window + /// + /// A descriptive text for where teh credentials being asked are used for + /// NetworkCredential object containing the user name, + public static NetworkCredential PromptForCredentialsConsole(string target) + { + var user = String.Empty; + var password = String.Empty; + var domain = String.Empty; + + // Setup the flags and variables + StringBuilder userPassword = new StringBuilder (), userID = new StringBuilder (); bool save = true; - NativeStructs.CredentialUIFlags flags = NativeStructs.CredentialUIFlags.COMPLETE_USERNAME | NativeStructs.CredentialUIFlags.PERSIST | NativeStructs.CredentialUIFlags.EXCLUDE_CERTIFICATES; + NativeCode.CredentialUIFlags flags = NativeCode.CredentialUIFlags.CompleteUsername | NativeCode.CredentialUIFlags.ExcludeCertificates; // Prompt the user - NativeStructs.CredentialUIReturnCodes returnCode = NativeStructs.CredUIPromptForCredentials (ref credUI, target, IntPtr.Zero, 0, userID, 100, userPassword, 100, ref save, flags); + NativeCode.CredentialUIReturnCodes returnCode = NativeCode.CredUICmdLinePromptForCredentials (target, IntPtr.Zero, 0, userID, 100, userPassword, 100, ref save, flags); password = userPassword.ToString (); StringBuilder userBuilder = new StringBuilder (); StringBuilder domainBuilder = new StringBuilder (); - returnCode = NativeStructs.CredUIParseUserName (userID.ToString (), userBuilder, int.MaxValue, domainBuilder, int.MaxValue); + returnCode = NativeCode.CredUIParseUserName (userID.ToString (), userBuilder, int.MaxValue, domainBuilder, int.MaxValue); switch ( returnCode ) { - case NativeStructs.CredentialUIReturnCodes.NO_ERROR: // The username is valid. + case NativeCode.CredentialUIReturnCodes.Success: // The username is valid. user = userBuilder.ToString (); domain = domainBuilder.ToString (); - return true; + break; - case NativeStructs.CredentialUIReturnCodes.ERROR_INVALID_ACCOUNT_NAME: // The username is not valid. + case NativeCode.CredentialUIReturnCodes.InvalidAccountName: // The username is not valid. user = userID.ToString (); domain = null; - return false; + break; - case NativeStructs.CredentialUIReturnCodes.ERROR_INSUFFICIENT_BUFFER: // One of the buffers is too small. + case NativeCode.CredentialUIReturnCodes.InsufficientBuffer: // One of the buffers is too small. throw new OutOfMemoryException (); - case NativeStructs.CredentialUIReturnCodes.ERROR_INVALID_PARAMETER: // ulUserMaxChars or ulDomainMaxChars is zero OR userName, user, or domain is NULL. + case NativeCode.CredentialUIReturnCodes.InvalidParameter: // ulUserMaxChars or ulDomainMaxChars is zero OR userName, user, or domain is NULL. throw new ArgumentNullException ("userName"); - default: - user = null; - domain = null; - return false; } + return new NetworkCredential (user, password, domain); } @@ -118,10 +208,10 @@ public static bool SaveCredentials(string Target, NetworkCredential credential) // Go ahead with what we have are stuff it into the CredMan structures. Credential cred = new Credential (credential); cred.TargetName = Target; - cred.Persist = NativeStructs.Persistance.ENTERPRISE; - NativeStructs.NativeCredential ncred = cred.GetNativeCredential (); + cred.Persist = NativeCode.Persistance.Entrprise; + NativeCode.NativeCredential ncred = cred.GetNativeCredential (); // Write the info into the CredMan storage. - bool written = NativeStructs.CredWrite (ref ncred, 0); + bool written = NativeCode.CredWrite (ref ncred, 0); int lastError = Marshal.GetLastWin32Error (); if ( written ) { @@ -148,7 +238,7 @@ public static NetworkCredential GetCredentials(string Target) var domain = String.Empty; // Make the API call using the P/Invoke signature - bool ret = NativeStructs.CredRead (Target, NativeStructs.CredentialType.GENERIC, 0, out nCredPtr); + bool ret = NativeCode.CredRead (Target, NativeCode.CredentialType.Generic, 0, out nCredPtr); int lastError = Marshal.GetLastWin32Error (); if ( !ret ) throw new Win32Exception (lastError, "CredDelete throw an error"); @@ -163,12 +253,12 @@ public static NetworkCredential GetCredentials(string Target) var user = cred.UserName; StringBuilder userBuilder = new StringBuilder (); StringBuilder domainBuilder = new StringBuilder (); - var ret1 = NativeStructs.CredUIParseUserName (user, userBuilder, int.MaxValue, domainBuilder, int.MaxValue); + var ret1 = NativeCode.CredUIParseUserName (user, userBuilder, int.MaxValue, domainBuilder, int.MaxValue); lastError = Marshal.GetLastWin32Error (); //assuming invalid account name to be not meeting condition for CredUIParseUserName //"The name must be in UPN or down-level format, or a certificate" - if ( ret1 == NativeStructs.CredentialUIReturnCodes.ERROR_INVALID_ACCOUNT_NAME ) + if ( ret1 == NativeCode.CredentialUIReturnCodes.InvalidAccountName ) userBuilder.Append (user); else if ( (uint) ret1 > 0 ) throw new Win32Exception (lastError, "CredUIParseUserName throw an error"); @@ -190,7 +280,7 @@ public static NetworkCredential GetCredentials(string Target) public static bool RemoveCredentials(string Target) { // Make the API call using the P/Invoke signature - var ret = NativeStructs.CredDelete (Target, NativeStructs.CredentialType.GENERIC, 0); + var ret = NativeCode.CredDelete (Target, NativeCode.CredentialType.Generic, 0); int lastError = Marshal.GetLastWin32Error (); if ( !ret ) throw new Win32Exception (lastError, "CredDelete throw an error"); diff --git a/AdysTech.CredentialManager/CriticalCredentialHandle.cs b/AdysTech.CredentialManager/CriticalCredentialHandle.cs index 35fc1ca..065dece 100644 --- a/AdysTech.CredentialManager/CriticalCredentialHandle.cs +++ b/AdysTech.CredentialManager/CriticalCredentialHandle.cs @@ -21,8 +21,8 @@ internal Credential GetCredential() if ( !IsInvalid ) { // Get the Credential from the mem location - NativeStructs.NativeCredential ncred = (NativeStructs.NativeCredential) Marshal.PtrToStructure (handle, - typeof (NativeStructs.NativeCredential)); + NativeCode.NativeCredential ncred = (NativeCode.NativeCredential) Marshal.PtrToStructure (handle, + typeof (NativeCode.NativeCredential)); // Create a managed Credential type and fill it with data from the native counterpart. Credential cred = new Credential (ncred); @@ -46,7 +46,7 @@ override protected bool ReleaseHandle() { // NOTE: We should also ZERO out the memory allocated to the handle, before free'ing it // so there are no traces of the sensitive data left in memory. - NativeStructs.CredFree (handle); + NativeCode.CredFree (handle); // Mark the handle as invalid for future users. SetHandleAsInvalid (); return true; diff --git a/AdysTech.CredentialManager/NativeStructs.cs b/AdysTech.CredentialManager/NativeStructs.cs deleted file mode 100644 index 951f2ca..0000000 --- a/AdysTech.CredentialManager/NativeStructs.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; - -namespace AdysTech.CredentialManager -{ - internal static class NativeStructs - { - [Flags] - internal enum CredentialUIFlags - { - INCORRECT_PASSWORD = 0x1, - DO_NOT_PERSIST = 0x2, - REQUEST_ADMINISTRATOR = 0x4, - EXCLUDE_CERTIFICATES = 0x8, - REQUIRE_CERTIFICATE = 0x10, - SHOW_SAVE_CHECK_BOX = 0x40, - ALWAYS_SHOW_UI = 0x80, - REQUIRE_SMARTCARD = 0x100, - PASSWORD_ONLY_OK = 0x200, - VALIDATE_USERNAME = 0x400, - COMPLETE_USERNAME = 0x800, - PERSIST = 0x1000, - SERVER_CREDENTIAL = 0x4000, - EXPECT_CONFIRMATION = 0x20000, - GENERIC_CREDENTIALS = 0x40000, - USERNAME_TARGET_CREDENTIALS = 0x80000, - KEEP_USERNAME = 0x100000, - } - - internal enum CredentialUIReturnCodes : uint - { - NO_ERROR = 0, - ERROR_CANCELLED = 1223, - ERROR_NO_SUCH_LOGON_SESSION = 1312, - ERROR_NOT_FOUND = 1168, - ERROR_INVALID_ACCOUNT_NAME = 1315, - ERROR_INSUFFICIENT_BUFFER = 122, - ERROR_INVALID_PARAMETER = 87, - ERROR_INVALID_FLAGS = 1004, - } - - [StructLayout (LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct CredentialUIInfo - { - public int cbSize; - public IntPtr hwndParent; - public string pszMessageText; - public string pszCaptionText; - public IntPtr hbmBanner; - } - - [DllImport ("credui")] - internal static extern CredentialUIReturnCodes CredUIPromptForCredentials(ref CredentialUIInfo creditUR, - string targetName, - IntPtr reserved1, - int iError, - StringBuilder userName, - int maxUserName, - StringBuilder password, - int maxPassword, - [MarshalAs (UnmanagedType.Bool)] ref bool pfSave, - CredentialUIFlags flags); - - [DllImport ("credui.dll", EntryPoint = "CredUIParseUserNameW", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern CredentialUIReturnCodes CredUIParseUserName( - string userName, - StringBuilder user, - int userMaxChars, - StringBuilder domain, - int domainMaxChars); - - internal enum CredentialType : uint - { - GENERIC = 1, - DOMAIN_PASSWORD = 2, - DOMAIN_CERTIFICATE = 3 - } - - internal enum Persistance : uint - { - SESSION = 1, - LOCAL_MACHINE = 2, - ENTERPRISE = 3 - } - - [StructLayout (LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct NativeCredential - { - public UInt32 Flags; - public CredentialType 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; - - } - - [DllImport ("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern bool CredDelete(string target, CredentialType type, int reservedFlag); - - [DllImport ("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr CredentialPtr); - - [DllImport ("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern bool CredWrite([In] ref NativeCredential userCredential, [In] UInt32 flags); - - [DllImport ("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] - internal static extern bool CredFree([In] IntPtr cred); - } -} diff --git a/CredentialManager.Test/CredentialManagerTest.cs b/CredentialManager.Test/CredentialManagerTest.cs index b4aaa2e..a9b759a 100644 --- a/CredentialManager.Test/CredentialManagerTest.cs +++ b/CredentialManager.Test/CredentialManagerTest.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using AdysTech.CredentialManager; using System.Net; +using System.Diagnostics; namespace CredentialManagerTest { @@ -15,6 +16,8 @@ public void TestSaveCredentials() { var cred = new NetworkCredential ("TestUser", "Pwd"); Assert.IsTrue (CredentialManager.SaveCredentials ("TestSystem", cred), "SaveCredential failed"); + + } catch ( Exception e ) { @@ -31,7 +34,8 @@ public void TestGetCredentials() try { - Assert.IsNotNull (CredentialManager.GetCredentials ("localhost:8086"), "GetCredential failed"); + Assert.IsNotNull (CredentialManager.GetCredentials ("TestSystem"), "GetCredential failed"); + } catch ( Exception e ) { @@ -40,5 +44,75 @@ public void TestGetCredentials() return; } } + + [TestMethod] + public void TestPromptForCredentials() + { + + try + { + bool save = false; + Assert.IsNotNull (CredentialManager.PromptForCredentials ("Some Webservice", ref save, "Please provide credentials", "Credentials for service"), "PromptForCredentials failed"); + + } + catch ( Exception e ) + { + Assert.Fail ("Unexpected exception of type {0} caught: {1}", + e.GetType (), e.Message); + return; + } + } + + /// + /// Not working as Console window can't be seen during test + /// + [TestMethod] + public void TestPromptForCredentialsConsole() + { + + try + { + bool save = false; + Assert.IsNotNull (CredentialManager.PromptForCredentialsConsole ("Some Webservice"), "PromptForCredentialsConsole failed"); + + } + catch ( Exception e ) + { + Assert.Fail ("Unexpected exception of type {0} caught: {1}", + e.GetType (), e.Message); + return; + } + } + [TestMethod] + public void IntegrationTest() + { + + try + { + bool save = true; + var cred = CredentialManager.PromptForCredentials ("Some Webservice", ref save, "Please provide credentials", "Credentials for service"); + Assert.IsNotNull (cred, "PromptForCredentials failed"); + if ( save ) + { + var usr = cred.UserName; + var pwd = cred.Password; + var dmn = cred.Domain; + Debug.WriteLine ("Usr:{0}, Pwd{1}, Dmn{2}", usr, pwd, dmn); + Assert.IsTrue (CredentialManager.SaveCredentials ("TestSystem", cred), "SaveCredential failed"); + cred = CredentialManager.GetCredentials ("TestSystem"); + Assert.IsNotNull (cred, "GetCredential failed"); + Assert.IsTrue (usr == cred.UserName && pwd == cred.Password && dmn == cred.Domain, "Saved and retreived data doesn't match"); + } + + } + catch ( Exception e ) + { + Assert.Fail ("Unexpected exception of type {0} caught: {1} on {2}", + e.GetType (), e.Message, e.StackTrace); + return; + } + } + + } }