From a40c787cbc4ad9b1148a32dfa3f9e898159cda06 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Fri, 19 Aug 2022 13:29:43 +0200 Subject: [PATCH 1/4] Module now using primarily securestring instead of clear passwords, extended unittests, extended max password length to 5 * 512 instead of 512 bytes --- PSCredentialManager.Api/CredentialManager.cs | 8 +-- .../CriticalCredentialHandle.cs | 16 +++-- .../Extensions/CredentialExtensions.cs | 4 +- .../Extensions/NativeCredentialExtensions.cs | 45 ++++++++++++-- .../CredentialManagerTests.cs | 12 ++-- .../Extensions/CredentialExtensionsTests.cs | 8 +++ .../NativeCredentialExtensionsTests.cs | 62 ++++++++++++++++++- .../Extensions/SecureStringExtensions.cs | 25 ++++++++ .../Extensions/StringExtensions.cs | 19 ++++++ .../PSCredentialManager.ApiTests.csproj | 2 + PSCredentialManager.Cmdlet/Cmdlets.cs | 47 +++++++++----- .../Extensions/CredentialExtensions.cs | 16 ++--- .../PSCredentialManager.Cmdlet.csproj | 3 +- .../Properties/AssemblyInfo.cs | 4 +- .../Extensions/CredentialExtensionsTests.cs | 23 ++++++- .../PSCredentialManager.CmdletTests.csproj | 2 +- PSCredentialManager.Object/Credential.cs | 2 + PSCredentialManager.sln | 7 ++- 18 files changed, 251 insertions(+), 54 deletions(-) create mode 100644 PSCredentialManager.ApiTests/Extensions/SecureStringExtensions.cs create mode 100644 PSCredentialManager.ApiTests/Extensions/StringExtensions.cs diff --git a/PSCredentialManager.Api/CredentialManager.cs b/PSCredentialManager.Api/CredentialManager.cs index 74c448a..9cc5323 100644 --- a/PSCredentialManager.Api/CredentialManager.cs +++ b/PSCredentialManager.Api/CredentialManager.cs @@ -21,7 +21,7 @@ public void WriteCred(NativeCredential credential) } } - public Credential ReadCred(string target, CredType type) + public Credential ReadCred(string target, CredType type, bool includeClearPassword, bool includeSecurePassword) { IntPtr nativeCredentialPointer; @@ -31,7 +31,7 @@ public Credential ReadCred(string target, CredType type) { using (CriticalCredentialHandle critCred = new CriticalCredentialHandle(nativeCredentialPointer)) { - return critCred.GetCredential(); + return critCred.GetCredential(includeClearPassword, includeSecurePassword); } } else @@ -62,7 +62,7 @@ public void DeleteCred(string target, CredType type) } } - public IEnumerable ReadCred() + public IEnumerable ReadCred(bool includeClearPassword, bool includeSecurePassword) { int count; int flags; @@ -84,7 +84,7 @@ public IEnumerable ReadCred() if (read) { CriticalCredentialHandle credHandle = new CriticalCredentialHandle(pCredentials); - return credHandle.GetCredentials(count); + return credHandle.GetCredentials(count, includeClearPassword, includeSecurePassword); } else { diff --git a/PSCredentialManager.Api/CriticalCredentialHandle.cs b/PSCredentialManager.Api/CriticalCredentialHandle.cs index fbbdb41..4b4a7ef 100644 --- a/PSCredentialManager.Api/CriticalCredentialHandle.cs +++ b/PSCredentialManager.Api/CriticalCredentialHandle.cs @@ -13,7 +13,7 @@ internal CriticalCredentialHandle(IntPtr preexistingHandle) SetHandle(preexistingHandle); } - internal Credential GetCredential() + internal Credential GetCredential(bool includeClearPassword, bool includeSecurePassword) { if (!IsInvalid) { @@ -21,7 +21,12 @@ internal Credential GetCredential() NativeCredential nativeCredential = (NativeCredential)Marshal.PtrToStructure(handle, typeof(NativeCredential)); // Create a managed Credential type and fill it with data from the native counterpart. - Credential credential = nativeCredential.ToCredential(); + Credential credential = nativeCredential.ToCredential(includeClearPassword, includeSecurePassword); + + nativeCredential = new NativeCredential(); + + GC.Collect(); + return credential; } else @@ -46,7 +51,7 @@ protected override bool ReleaseHandle() return false; } - public Credential[] GetCredentials(int count) + public Credential[] GetCredentials(int count, bool includeClearPassword, bool includeSecurePassword) { if (IsInvalid) { @@ -58,9 +63,12 @@ public Credential[] GetCredentials(int count) { IntPtr pCred = Marshal.ReadIntPtr(handle, inx * IntPtr.Size); NativeCredential nativeCredential = (NativeCredential)Marshal.PtrToStructure(pCred, typeof(NativeCredential)); - Credential credential = nativeCredential.ToCredential(); + Credential credential = nativeCredential.ToCredential(includeClearPassword, includeSecurePassword); credentials[inx] = credential; + nativeCredential = new NativeCredential(); + GC.Collect(); } + GC.Collect(); return credentials; } diff --git a/PSCredentialManager.Api/Extensions/CredentialExtensions.cs b/PSCredentialManager.Api/Extensions/CredentialExtensions.cs index 33484e3..a88056c 100644 --- a/PSCredentialManager.Api/Extensions/CredentialExtensions.cs +++ b/PSCredentialManager.Api/Extensions/CredentialExtensions.cs @@ -20,9 +20,9 @@ public static NativeCredential ToNativeCredential(this Credential credential) TargetAlias = IntPtr.Zero, Type = credential.Type, Persist = (uint)credential.Persist, - CredentialBlobSize = credential.PaswordSize, + CredentialBlobSize = credential.SecurePassword != null ? (uint)credential.SecurePassword.Length * 2 : credential.PaswordSize, TargetName = Marshal.StringToCoTaskMemUni(credential.TargetName), - CredentialBlob = Marshal.StringToCoTaskMemUni(credential.Password), + CredentialBlob = credential.SecurePassword != null ? Marshal.SecureStringToCoTaskMemUnicode(credential.SecurePassword) : Marshal.StringToCoTaskMemUni(credential.Password), UserName = Marshal.StringToCoTaskMemUni(credential.UserName), LastWritten = credential.LastWritten.ToComFileTime() }; diff --git a/PSCredentialManager.Api/Extensions/NativeCredentialExtensions.cs b/PSCredentialManager.Api/Extensions/NativeCredentialExtensions.cs index accf6c1..fdc64f9 100644 --- a/PSCredentialManager.Api/Extensions/NativeCredentialExtensions.cs +++ b/PSCredentialManager.Api/Extensions/NativeCredentialExtensions.cs @@ -7,7 +7,7 @@ namespace PSCredentialManager.Api.Extensions { public static class NativeCredentialExtensions { - public static Credential ToCredential(this NativeCredential nativeCredential) + public static Credential ToCredential(this NativeCredential nativeCredential, bool includeClearPassword, bool includeSecurePassword) { Credential credential; @@ -22,13 +22,50 @@ public static Credential ToCredential(this NativeCredential nativeCredential) TargetName = Marshal.PtrToStringUni(nativeCredential.TargetName), TargetAlias = Marshal.PtrToStringUni(nativeCredential.TargetAlias), Comment = Marshal.PtrToStringUni(nativeCredential.Comment), - PaswordSize = nativeCredential.CredentialBlobSize, - LastWritten = nativeCredential.LastWritten.ToDateTime() + LastWritten = nativeCredential.LastWritten.ToDateTime(), + SecurePassword = null, + PaswordSize = uint.MinValue, + Password = null, }; if (0 < nativeCredential.CredentialBlobSize) { - credential.Password = Marshal.PtrToStringUni(nativeCredential.CredentialBlob, (int)nativeCredential.CredentialBlobSize / 2); + if(includeClearPassword) + { + credential.PaswordSize = nativeCredential.CredentialBlobSize; + credential.Password = Marshal.PtrToStringUni(nativeCredential.CredentialBlob, (int)nativeCredential.CredentialBlobSize / 2); + } + + if(includeSecurePassword) + { + credential.SecurePassword = new System.Security.SecureString(); + + for(int i=0, size = (int)nativeCredential.CredentialBlobSize / 2; i< size; i++) + { + string singlesign = Marshal.PtrToStringUni(nativeCredential.CredentialBlob + (i*2), 1); + if(singlesign.Length > 0) + { + credential.SecurePassword.AppendChar(singlesign[0]); + } + singlesign = null; + GC.Collect(); + } + + GC.Collect(); + } + } + else + { + if(includeClearPassword) + { + credential.PaswordSize = nativeCredential.CredentialBlobSize; + credential.Password = ""; + } + + if(includeSecurePassword) + { + credential.SecurePassword = new System.Security.SecureString(); + } } } catch (Exception ex) diff --git a/PSCredentialManager.ApiTests/CredentialManagerTests.cs b/PSCredentialManager.ApiTests/CredentialManagerTests.cs index 0542e10..8f2f4ea 100644 --- a/PSCredentialManager.ApiTests/CredentialManagerTests.cs +++ b/PSCredentialManager.ApiTests/CredentialManagerTests.cs @@ -51,13 +51,13 @@ public void ReadCredTest() }; - ShimCriticalCredentialHandle.AllInstances.GetCredential = - (CriticalCredentialHandle criticalCredentialHandle) => + ShimCriticalCredentialHandle.AllInstances.GetCredentialBooleanBoolean = + (CriticalCredentialHandle criticalCredentialHandle, bool includeClearPassword, bool includeSecurePassword) => { return new Credential(); }; - manager.ReadCred("server01", CredType.Generic); + manager.ReadCred("server01", CredType.Generic, true, true); } } @@ -95,13 +95,13 @@ public void ReadCredTest1() }; - ShimCriticalCredentialHandle.AllInstances.GetCredentialsInt32 = - (CriticalCredentialHandle credentialHandle, int count) => + ShimCriticalCredentialHandle.AllInstances.GetCredentialsInt32BooleanBoolean = + (CriticalCredentialHandle credentialHandle, int count, bool includeClearPassword, bool includeSecurePassword) => { return new Credential[count]; }; - manager.ReadCred(); + manager.ReadCred(true, true); } } } diff --git a/PSCredentialManager.ApiTests/Extensions/CredentialExtensionsTests.cs b/PSCredentialManager.ApiTests/Extensions/CredentialExtensionsTests.cs index 7352979..de77170 100644 --- a/PSCredentialManager.ApiTests/Extensions/CredentialExtensionsTests.cs +++ b/PSCredentialManager.ApiTests/Extensions/CredentialExtensionsTests.cs @@ -31,6 +31,14 @@ public void ToNativeCredentialTest() Assert.IsNotNull(nativeCredential); Assert.IsInstanceOfType(nativeCredential, typeof(NativeCredential)); + string ncUserName = System.Runtime.InteropServices.Marshal.PtrToStringUni(nativeCredential.UserName); + Assert.IsNotNull(ncUserName); + Assert.AreEqual(credential.UserName, ncUserName); + + Credential convertedOnlyClear = nativeCredential.ToCredential(true, false); + Assert.IsNotNull(convertedOnlyClear); + Assert.IsInstanceOfType(convertedOnlyClear, typeof(Credential)); + Assert.AreEqual(credential.Password, convertedOnlyClear.Password); } } diff --git a/PSCredentialManager.ApiTests/Extensions/NativeCredentialExtensionsTests.cs b/PSCredentialManager.ApiTests/Extensions/NativeCredentialExtensionsTests.cs index 7c6879d..136a06e 100644 --- a/PSCredentialManager.ApiTests/Extensions/NativeCredentialExtensionsTests.cs +++ b/PSCredentialManager.ApiTests/Extensions/NativeCredentialExtensionsTests.cs @@ -28,10 +28,70 @@ public void ToCredentialTest() UserName = new IntPtr(0) }; - Credential credential = nativeCredential.ToCredential(); + Credential credential = nativeCredential.ToCredential(true, true); Assert.IsNotNull(credential); Assert.IsInstanceOfType(credential, typeof(Credential)); } + + [TestMethod()] + public void ToCredentialTestFilledValues() + { + Credential credential = new Credential() + { + AttributeCount = 0, + Attributes = new IntPtr(0), + Comment = "This is a comment", + Password = "April123!!", + PaswordSize = 20, + Flags = 0, + LastWritten = DateTime.Now, + Persist = CredPersist.LocalMachine, + TargetName = "server01", + Type = CredType.Generic, + UserName = "test-user" + }; + + NativeCredential nativeCredential = credential.ToNativeCredential(); + + Assert.IsNotNull(nativeCredential); + Assert.IsInstanceOfType(nativeCredential, typeof(NativeCredential)); + string ncUserName = System.Runtime.InteropServices.Marshal.PtrToStringUni(nativeCredential.UserName); + Assert.IsNotNull(ncUserName); + Assert.AreEqual(credential.UserName, ncUserName); + + Credential convertedOnlyClear = nativeCredential.ToCredential(true, false); + Assert.IsNotNull(convertedOnlyClear); + Assert.IsInstanceOfType(convertedOnlyClear, typeof(Credential)); + Assert.IsNotNull(convertedOnlyClear.UserName); + Assert.AreEqual(credential.UserName, convertedOnlyClear.UserName); + Assert.IsNotNull(convertedOnlyClear.Password); + Assert.AreEqual(credential.Password, convertedOnlyClear.Password); + Assert.AreEqual(credential.PaswordSize, convertedOnlyClear.PaswordSize); + Assert.IsNull(convertedOnlyClear.SecurePassword); + + Credential convertedOnlySecure = nativeCredential.ToCredential(false, true); + Assert.IsNotNull(convertedOnlySecure); + Assert.IsInstanceOfType(convertedOnlySecure, typeof(Credential)); + Assert.IsNotNull(convertedOnlySecure.UserName); + Assert.AreEqual(credential.UserName, convertedOnlySecure.UserName); + Assert.IsNotNull(convertedOnlySecure.SecurePassword); + Assert.AreEqual(credential.Password, convertedOnlySecure.SecurePassword.ToInsecureString()); + Assert.AreEqual(uint.MinValue, convertedOnlySecure.PaswordSize); + Assert.IsNull(convertedOnlySecure.Password); + + Credential convertedClearAndSecure = nativeCredential.ToCredential(true, true); + Assert.IsNotNull(convertedClearAndSecure); + Assert.IsInstanceOfType(convertedClearAndSecure, typeof(Credential)); + Assert.IsNotNull(convertedClearAndSecure.UserName); + Assert.AreEqual(credential.UserName, convertedClearAndSecure.UserName); + Assert.IsNotNull(convertedClearAndSecure.Password); + Assert.IsNotNull(convertedClearAndSecure.SecurePassword); + Assert.AreEqual(credential.Password, convertedClearAndSecure.Password); + Assert.AreEqual(credential.PaswordSize, convertedClearAndSecure.PaswordSize); + Assert.AreEqual(credential.Password, convertedClearAndSecure.SecurePassword.ToInsecureString()); + Assert.AreEqual(credential.PaswordSize, (uint)convertedClearAndSecure.SecurePassword.Length * 2); + + } } } \ No newline at end of file diff --git a/PSCredentialManager.ApiTests/Extensions/SecureStringExtensions.cs b/PSCredentialManager.ApiTests/Extensions/SecureStringExtensions.cs new file mode 100644 index 0000000..9d95ac9 --- /dev/null +++ b/PSCredentialManager.ApiTests/Extensions/SecureStringExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace PSCredentialManager.ApiTests.Extensions +{ + public static class SecureStringExtensions + { + public static string ToInsecureString(this SecureString secureString) + { + IntPtr secureStringPtr = IntPtr.Zero; + + try + { + secureStringPtr = Marshal.SecureStringToGlobalAllocUnicode(secureString); + return Marshal.PtrToStringUni(secureStringPtr); + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(secureStringPtr); + } + + } + } +} diff --git a/PSCredentialManager.ApiTests/Extensions/StringExtensions.cs b/PSCredentialManager.ApiTests/Extensions/StringExtensions.cs new file mode 100644 index 0000000..c0270d7 --- /dev/null +++ b/PSCredentialManager.ApiTests/Extensions/StringExtensions.cs @@ -0,0 +1,19 @@ +using System.Security; + +namespace PSCredentialManager.ApiTests.Extensions +{ + public static class StringExtensions + { + public static SecureString ToSecureString(this string insecureString) + { + SecureString secureString = new SecureString(); + + foreach (char character in insecureString) + { + secureString.AppendChar(character); + } + + return secureString; + } + } +} diff --git a/PSCredentialManager.ApiTests/PSCredentialManager.ApiTests.csproj b/PSCredentialManager.ApiTests/PSCredentialManager.ApiTests.csproj index 608a24e..140a5ad 100644 --- a/PSCredentialManager.ApiTests/PSCredentialManager.ApiTests.csproj +++ b/PSCredentialManager.ApiTests/PSCredentialManager.ApiTests.csproj @@ -60,6 +60,8 @@ + + diff --git a/PSCredentialManager.Cmdlet/Cmdlets.cs b/PSCredentialManager.Cmdlet/Cmdlets.cs index 0221528..fe2b9ed 100644 --- a/PSCredentialManager.Cmdlet/Cmdlets.cs +++ b/PSCredentialManager.Cmdlet/Cmdlets.cs @@ -58,6 +58,18 @@ public class GetStoredCredential : PSCmdlet [Parameter()] public CredType Type = CredType.Generic; + /// + /// Switch to not fill the property Password with the clear password and PasswordSize with the unicode size of the clear password + /// + [Parameter()] + public SwitchParameter ExcludeClearPassword; + + /// + /// Switch to fill the property SecurePassword with the password as SecureString, may need more time for execution + /// + [Parameter(ParameterSetName = "CredentialObject Output")] + public SwitchParameter IncludeSecurePassword; + /// /// Switch to return the credentials as Credential objects instead of the default PSObject /// @@ -79,7 +91,7 @@ protected override void ProcessRecord() IEnumerable credential; try { - credential = credentialManager.ReadCred(); + credential = credentialManager.ReadCred(!ExcludeClearPassword, IncludeSecurePassword); if (!AsCredentialObject) { @@ -95,9 +107,9 @@ protected override void ProcessRecord() PSCredential psCredential = cred.ToPsCredential(); WriteObject(psCredential); } - catch + catch(Exception ex) { - WriteWarning("Unable to convert Credential object without username or password to PSCredential object"); + WriteWarning(ex.Message); } } } @@ -121,7 +133,7 @@ protected override void ProcessRecord() { //Retrieve credential from Cred Store WriteVerbose("Retrieving requested credential from Windows Credential Manager"); - credential = credentialManager.ReadCred(Target, Type); + credential = credentialManager.ReadCred(Target, Type, !ExcludeClearPassword, IncludeSecurePassword); } catch (CredentialNotFoundException exception) { @@ -145,9 +157,9 @@ protected override void ProcessRecord() PSCredential psCredential = credential.ToPsCredential(); WriteObject(psCredential); } - catch + catch(Exception ex) { - WriteWarning("Unable to convert Credential object without username or password to PSCredential object"); + WriteWarning(ex.Message); } } else @@ -192,6 +204,8 @@ protected override void EndProcessing() { } [OutputType(typeof(Credential))] public class NewStoredCredential : PSCmdlet { + private const uint c_maxPasswordBytes = 2560; // CRED_MAX_CREDENTIAL_BLOB_SIZE (5 * 512) => 2560 + /// /// Specifies the target of the credentials being added. /// @@ -213,7 +227,7 @@ public class NewStoredCredential : PSCmdlet public string Password = Helpers.GeneratePassword(10, 2); /// - /// Specifies the password as a secure string, cannot be used in conjunction with SecurePassword or Credential parameters. + /// Specifies the password as a secure string, cannot be used in conjunction with Password or Credential parameters. /// [Parameter(ParameterSetName = "Secure String")] public SecureString SecurePassword; @@ -253,11 +267,11 @@ protected override void BeginProcessing() if (MyInvocation.BoundParameters.ContainsKey("Password")) { //Argument validation - //Check password does not exceed 512 bytes + //Check password does not exceed 2560 bytes byte[] byteArray = Encoding.Unicode.GetBytes(Password); - if (byteArray.Length > 512) + if (byteArray.Length > c_maxPasswordBytes) { - Exception exception = new ArgumentOutOfRangeException($"Password", "The specified password has exceeded 512 bytes"); + Exception exception = new ArgumentOutOfRangeException($"Password", $"The specified password has exceeded {c_maxPasswordBytes} bytes"); ErrorRecord error = new ErrorRecord(exception, "1", ErrorCategory.InvalidArgument, Password); WriteError(error); } @@ -266,11 +280,11 @@ protected override void BeginProcessing() if (MyInvocation.BoundParameters.ContainsKey("SecurePassword")) { //Argument validation - //Check password does not exceed 512 bytes + //Check password does not exceed 2560 bytes byte[] byteArray = Encoding.Unicode.GetBytes(Password); - if (byteArray.Length > 512) + if (byteArray.Length > c_maxPasswordBytes) { - Exception exception = new ArgumentOutOfRangeException($"SecurePassword", "The specified password has exceeded 512 bytes"); + Exception exception = new ArgumentOutOfRangeException($"SecurePassword", $"The specified password has exceeded {c_maxPasswordBytes} bytes"); ErrorRecord error = new ErrorRecord(exception, "1", ErrorCategory.InvalidArgument, Password); WriteError(error); } @@ -283,7 +297,7 @@ protected override void ProcessRecord() if (MyInvocation.BoundParameters.ContainsKey("Credentials")) { UserName = Credentials.UserName; - Password = Credentials.GetNetworkCredential().Password; + SecurePassword = Credentials.Password; } //Create credential object @@ -291,7 +305,8 @@ protected override void ProcessRecord() { TargetName = Target, Password = Password, - PaswordSize = (UInt32)Encoding.Unicode.GetBytes(Password).Length, + SecurePassword = SecurePassword, + PaswordSize = Password != null ? (UInt32)Encoding.Unicode.GetBytes(Password).Length : uint.MinValue, AttributeCount = 0, Attributes = IntPtr.Zero, Comment = Comment, @@ -302,8 +317,6 @@ protected override void ProcessRecord() LastWritten = DateTime.Now, }; - - //Convert credential to native credential NativeCredential nativeCredential = credential.ToNativeCredential(); CredentialManager credentialManager = new CredentialManager(); diff --git a/PSCredentialManager.Cmdlet/Extensions/CredentialExtensions.cs b/PSCredentialManager.Cmdlet/Extensions/CredentialExtensions.cs index 66dabd8..11fa51b 100644 --- a/PSCredentialManager.Cmdlet/Extensions/CredentialExtensions.cs +++ b/PSCredentialManager.Cmdlet/Extensions/CredentialExtensions.cs @@ -10,20 +10,20 @@ public static PSCredential ToPsCredential(this Credential credential) { PSCredential psCredential; - try + if (credential.UserName != null && (!string.IsNullOrEmpty(credential.Password) || credential.SecurePassword != null)) { - if (credential.UserName != null && credential.Password != null) - { - psCredential = new PSCredential(credential.UserName, credential.Password.ToSecureString()); + try + { + psCredential = new PSCredential(credential.UserName, credential.SecurePassword ?? credential.Password.ToSecureString()); } - else + catch (Exception ex) { - throw new Exception("PSCredentialManager.Cmdlet.Utility.PSCredentialUtility.ConvertToPSCredential Unable to convert credential objects with no username"); + throw new Exception("Unable to convert credential object", ex); } } - catch (Exception ex) + else { - throw new Exception("PSCredentialManager.Cmdlet.Utility.PSCredentialUtility.ConvertToPSCredential Unable to convert credential object", ex); + throw new Exception("Unable to convert Credential object without username or password to PSCredential object"); } return psCredential; diff --git a/PSCredentialManager.Cmdlet/PSCredentialManager.Cmdlet.csproj b/PSCredentialManager.Cmdlet/PSCredentialManager.Cmdlet.csproj index 8db95b1..be771e8 100644 --- a/PSCredentialManager.Cmdlet/PSCredentialManager.Cmdlet.csproj +++ b/PSCredentialManager.Cmdlet/PSCredentialManager.Cmdlet.csproj @@ -24,8 +24,7 @@ DEBUG;TRACE prompt 4 - - + bin\Debug\TUN.CredentialManager.xml pdbonly diff --git a/PSCredentialManager.Cmdlet/Properties/AssemblyInfo.cs b/PSCredentialManager.Cmdlet/Properties/AssemblyInfo.cs index 63b2a64..8441b02 100644 --- a/PSCredentialManager.Cmdlet/Properties/AssemblyInfo.cs +++ b/PSCredentialManager.Cmdlet/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.1.0.0")] -[assembly: AssemblyFileVersion("2.1.0.0")] +[assembly: AssemblyVersion("3.0.0.0")] +[assembly: AssemblyFileVersion("3.0.0.0")] diff --git a/PSCredentialManager.CmdletTests/Extensions/CredentialExtensionsTests.cs b/PSCredentialManager.CmdletTests/Extensions/CredentialExtensionsTests.cs index dc3f723..a15b075 100644 --- a/PSCredentialManager.CmdletTests/Extensions/CredentialExtensionsTests.cs +++ b/PSCredentialManager.CmdletTests/Extensions/CredentialExtensionsTests.cs @@ -22,11 +22,32 @@ public void ToPSCredentialTest() Assert.IsNotNull(psCredential); Assert.IsInstanceOfType(psCredential, typeof(PSCredential)); + Assert.AreEqual(credential.UserName, psCredential.UserName); + Assert.AreEqual(credential.Password, psCredential.Password.ToInsecureString()); + } + + [TestMethod()] + public void ToPSCredentialTestSecureString() + { + string pwd = "Password1"; + + Credential credential = new Credential() + { + UserName = "test-user", + SecurePassword = pwd.ToSecureString() + }; + + PSCredential psCredential = credential.ToPsCredential(); + + Assert.IsNotNull(psCredential); + Assert.IsInstanceOfType(psCredential, typeof(PSCredential)); + Assert.AreEqual(credential.UserName, psCredential.UserName); + Assert.AreEqual(pwd, psCredential.Password.ToInsecureString()); } [TestMethod()] [ExpectedException(typeof(Exception))] - public void ToPSCredentialTest1() + public void ToPSCredentialTestException() { Credential credential = new Credential() { diff --git a/PSCredentialManager.CmdletTests/PSCredentialManager.CmdletTests.csproj b/PSCredentialManager.CmdletTests/PSCredentialManager.CmdletTests.csproj index 0b0477f..b1421c4 100644 --- a/PSCredentialManager.CmdletTests/PSCredentialManager.CmdletTests.csproj +++ b/PSCredentialManager.CmdletTests/PSCredentialManager.CmdletTests.csproj @@ -72,7 +72,7 @@ PSCredentialManager.Api - {EB676C83-B382-4217-AAF9-C412B1E26B5F} + {eb676c83-b382-4217-aaf9-c412b1e26b5f} PSCredentialManager.Cmdlet diff --git a/PSCredentialManager.Object/Credential.cs b/PSCredentialManager.Object/Credential.cs index 1c03aba..c7e696a 100644 --- a/PSCredentialManager.Object/Credential.cs +++ b/PSCredentialManager.Object/Credential.cs @@ -1,6 +1,7 @@ using PSCredentialManager.Common.Enum; using System; using System.Runtime.InteropServices; +using System.Security; namespace PSCredentialManager.Common { @@ -14,6 +15,7 @@ public struct Credential public DateTime LastWritten; public uint PaswordSize; public string Password; + public SecureString SecurePassword; public CredPersist Persist; public uint AttributeCount; public IntPtr Attributes; diff --git a/PSCredentialManager.sln b/PSCredentialManager.sln index a611845..5015c4b 100644 --- a/PSCredentialManager.sln +++ b/PSCredentialManager.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32319.34 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FF227CA6-0204-4FC1-847F-485C136DCCFB}" ProjectSection(SolutionItems) = preProject @@ -49,4 +49,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {973570A8-B126-44FE-97AB-36116DEF39A3} + EndGlobalSection EndGlobal From 00dbfbafa3a816e99fea8cb19fa289be89df7f95 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Mon, 22 Aug 2022 11:06:26 +0200 Subject: [PATCH 2/4] - Expanded Get-StoredCredential with switches IncludeSecurePassword and ExcludeClearPassword - Better error messages if credentials to remove weren't found or if password in provided credentials object was too long - Extended README.md with new and additional information --- PSCredentialManager.Api/CredentialManager.cs | 10 +++++++-- PSCredentialManager.Cmdlet/Cmdlets.cs | 22 +++++++++++++++----- README.md | 20 ++++++++++++++++-- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/PSCredentialManager.Api/CredentialManager.cs b/PSCredentialManager.Api/CredentialManager.cs index 9cc5323..5e46aa8 100644 --- a/PSCredentialManager.Api/CredentialManager.cs +++ b/PSCredentialManager.Api/CredentialManager.cs @@ -57,8 +57,14 @@ public void DeleteCred(string target, CredType type) if (!delete) { - string message = $"DeleteCred failed with the error code {lastError}."; - throw new Exception(message); + if(lastError == 1168) + { + throw new CredentialNotFoundException($"DeleteCred failed with the error code {lastError} (credential not found)."); + } + else + { + throw new Exception($"DeleteCred failed with the error code {lastError}."); + } } } diff --git a/PSCredentialManager.Cmdlet/Cmdlets.cs b/PSCredentialManager.Cmdlet/Cmdlets.cs index fe2b9ed..0492def 100644 --- a/PSCredentialManager.Cmdlet/Cmdlets.cs +++ b/PSCredentialManager.Cmdlet/Cmdlets.cs @@ -33,6 +33,7 @@ namespace PSCredentialManager.Cmdlet /// LastWritten : 23/04/2016 10:01:37 /// PaswordSize : 18 /// Password : Password1 + /// SecurePassword : /// Persist : ENTERPRISE /// AttributeCount : 0 /// Attributes : 0 @@ -67,7 +68,7 @@ public class GetStoredCredential : PSCmdlet /// /// Switch to fill the property SecurePassword with the password as SecureString, may need more time for execution /// - [Parameter(ParameterSetName = "CredentialObject Output")] + [Parameter()] public SwitchParameter IncludeSecurePassword; /// @@ -189,6 +190,7 @@ protected override void EndProcessing() { } /// LastWritten : 23/04/2016 10:48:56 /// PaswordSize : 18 /// Password : Password1 + /// SecurePassword : /// Persist : SESSION /// AttributeCount : 0 /// Attributes : 0 @@ -279,16 +281,17 @@ protected override void BeginProcessing() if (MyInvocation.BoundParameters.ContainsKey("SecurePassword")) { + Password = null; + //Argument validation //Check password does not exceed 2560 bytes - byte[] byteArray = Encoding.Unicode.GetBytes(Password); - if (byteArray.Length > c_maxPasswordBytes) + int pwdlen = SecurePassword.Length * 2; + if (pwdlen > c_maxPasswordBytes) { Exception exception = new ArgumentOutOfRangeException($"SecurePassword", $"The specified password has exceeded {c_maxPasswordBytes} bytes"); - ErrorRecord error = new ErrorRecord(exception, "1", ErrorCategory.InvalidArgument, Password); + ErrorRecord error = new ErrorRecord(exception, "1", ErrorCategory.InvalidArgument, "SecurePassword"); WriteError(error); } - Password = SecurePassword.ToInsecureString(); } } @@ -298,6 +301,15 @@ protected override void ProcessRecord() { UserName = Credentials.UserName; SecurePassword = Credentials.Password; + Password = null; + + int pwdlen = SecurePassword.Length * 2; + if (pwdlen > c_maxPasswordBytes) + { + Exception exception = new ArgumentOutOfRangeException($"Credentials", $"The specified password has exceeded {c_maxPasswordBytes} bytes"); + ErrorRecord error = new ErrorRecord(exception, "1", ErrorCategory.InvalidArgument, "Credentials"); + WriteError(error); + } } //Create credential object diff --git a/README.md b/README.md index 6692db7..4537b7f 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,19 @@ The module is available on the PowerShell Gallery: https://www.powershellgallery ### Manual Installation 1. Dowload the latest verion of the module code from https://github.com/echalone/PowerShell_Credential_Manager/releases -2. Unzip TUN.CredentialManager.zip and copy the contents to you preferred module path. Usually C:\Users\$UserName\Documents\WindowsPowerShell\Modules. +2. Unzip TUN.CredentialManager.zip and copy the contents to you preferred module path. Usually C:\Users\UserName\Documents\WindowsPowerShell\Modules. 3. In your PowerShell session run the command Import-Module TUN.CredentialManager ## Usage -Import the module in to your PowerShell session and full help is available in the module with Get-Help +Import the module in to your PowerShell session and full help is available in the module with Get-Help. + +### New with version 3.0 +* Use New-StoredCredential with SecurePassword (of type secure string) or Credentials (of type PSCredential) parameter to use only secure string internally +* Use Get-StoredCredential with ExcludeClearPassword and IncludeSecurePassword switches to exclude the clear password being stored (even in memory) and to only retrieve and store the password as a secure string (including secure passwords may lengthen execution time) +* Excluding the clear password and only working with secure password/secure string will also set the PasswordSize to 0 +* Passwords up to 1280 unicode characters (2560 bytes) are now supported (up from 256 unicode characters) +* Notice: A breaking change with version 3.0 to previous versions is that New-StoredCredential will no longer return the clear password in the property Password of the returned object if you used the Credentials or SecurePassword parameters instead of the clear string Password parameter (if you really need the clear password you can always retrieve it with the Get-StoredProcedure default call afterwards, or just remember it beforehand) ## Contributing @@ -31,6 +38,15 @@ Import the module in to your PowerShell session and full help is available in th ## History +### v3.0 +- Extended possible password length to 1280 unicode characters (2560 bytes) +- Rewriting internal code to work mainly with secure strings if possible +- Expanded stored credential object with property SecurePassword to store password as secure string +- Expanded Get-StoredCredential with switches IncludeSecurePassword and ExcludeClearPassword +- Returning only clear password in New-StoredCredential if clear Password parameter was provided, but not if secure string SecurePassword parameter or PSCredential Credentials parameter was provided (in this case, only return secure string password in SecurePassword) +- Extended UnitTests with new test cases +- Better error messages if credentials to remove weren't found or if password in provided credentials object was too long + ### v2.1 - Explicit naming of cmdlets to export in psd1 for Powershell Core compatibility - Rewriting of some internal code to make it compatible with Powershell Core From 9c3297ddfc523a5238bcc1032e84345ada74a455 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Mon, 22 Aug 2022 11:53:40 +0200 Subject: [PATCH 3/4] Added names to copyright of dll, expanded gitignore with test files and added some more information to module manifest file --- .gitignore | 2 ++ .../Properties/AssemblyInfo.cs | 2 +- .../TUN.CredentialManager.psd1 | Bin 6144 -> 9430 bytes 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7964536..4b2e2d4 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ bld/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* +*.testlog +testlog.manifest #NUNIT *.VisualState.xml diff --git a/PSCredentialManager.Cmdlet/Properties/AssemblyInfo.cs b/PSCredentialManager.Cmdlet/Properties/AssemblyInfo.cs index 8441b02..0627ffe 100644 --- a/PSCredentialManager.Cmdlet/Properties/AssemblyInfo.cs +++ b/PSCredentialManager.Cmdlet/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TUN.CredentialManager")] -[assembly: AssemblyCopyright("Copyright © 2015 and 2022")] +[assembly: AssemblyCopyright("Copyright © 2015 Dave Garnar and 2022 Markus Szumovski")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/PSCredentialManager.Cmdlet/TUN.CredentialManager.psd1 b/PSCredentialManager.Cmdlet/TUN.CredentialManager.psd1 index 5b89f2ae3f6f236aaa61d93906714a7664212373..d31446a19ef4b5b638b6d7a8bd583c17b0be529d 100644 GIT binary patch literal 9430 zcmc(lYfoE85Qg`2rT&K_Q3+CoYZ7viDg`AWK?-RUNZN0a0o!p4m|_S?tNiu0@3Z5{ zo;}`UFcFF@<8#~DcV=hanVrLb|IURU!w+FDRAD;|!$CL?hoP&pqtFS*VGw#c8-+7n z>FNGyyire_BeW0>tU{3#tq8n+Yv*3pqrf7JP(dfrC6jMD5w z&BN?#VI?fz4ejihg5Wc&3>+Cj$P`Z&x_6=s=j=zkBgY-Bt+;+B{cWm|UB;r}gg0<~^-ApT)2) zuUeJ7#c)@jhdNu7ru?qNqX!zXqAT~q9ep3hvt9%FhR}h*)_g%PEa`ZtzvtqCDlD|H%Prk=t#@M#982;j@>^F3u*QO}+x(+3g1%;A z1eZP(u}SVp%@rxlQb~)U zv?t%zhVkK@;oAQcWM<+BVs<1q3hV_?JaXwS!S%ppAvHWnM zyHtPIS(~RCdnTD({hgFp*5$5HH0y}jR*7gn(ubI?#1q76KgKl?j~=iaF>d*9K{fWv z@FueL_59Dqm-Wfl#@No;&GI&N09nl{Z&>$SS3tTT+gY9?%l)qEW<7AUA>;=-zWWWB zCy{Kl;wVQ>VtM0f@-kiot64NtU&7H;F6ipJ5m?H2-8rtWbs8$u!|Rns9mfn~6-Ua) z!)i8bV$rLNiJi8y)iCqu7PvqRhirLO% zPSWf6N8+cU7$4v8IBHw9UFGDHs7G1nPg>8zvF7Yd%wO}hyNEd!CYjyWeEwN5Z$|%S zhe#}aAKY~evtVz=(qxQM-Qh1(DR6{U|8TYMxw)Iy5cRv%>8vPwY;0rWtqOdjan1en za<-eE^c-gea+Kp~YEh1<&6q_}jSOznw;MhQqwyUph^JEJweKP(Wr6frCdWAV=SDsM zM3UL>lVQIFK`y5}JFJMkDt+d)4`}*9)6g8WhZ5oZ95;DE6e^43}l^eS=n69RcC*x zxh;oVERvVW%=K6V6&;CvRfo2lBG;AsT`9gj;_6YBW-G^9x}T$|v*Y!VY)V%mD9utv zLCzV-4`F6<7qZx%z(jYtWmykX7qFa`xQtq2VDzfuX%~zASF$ z4P(<+wlxwS?&yxA8R-}%H;w9@`=zl3rdy91jtpzn3_T6-kxx>k?{CKPN}(Rl9Jfm{ zhJUAL)}-}HeBZbw8{ng8L-j%pS?{w>>)En5K9AYTc*DmIqZNFX+mecGdT8uwtY=0Y zRAT#_b8S~`QA)4Ct#S1?zgD-p&Z|*p=Achr?}{c?2AA=26+Ok?DPbY{8jSKcHTIqb z54KG2x;e@`bDNE>jvT%Qx4kvw-SAu4&?5#<-v8PK!$RbFu;4Qs;Xn5Fn!1$Ed)lY7 z_o5D))QS4tA_EV%==7@1cLZbxVyCYs?X4{ z2jo@_ay|DDbV_|U*3ouTmWPQ_e^ZPMe+R@nc|4LVoAL}8@CvH}mt&k-HQp6djdY!8 zZL6Dlg_T){D6pob?_5<7TC>LIk(MqcyLZ~n=@@k zpS1rpdD@theu*(Iy5#*$=h!d0V{$?2ewObR=nujs*xnceJD4rn#@BaEY2VQ<2@EfU zmc7E3tVU&KRovEBRPEkh8chYrSahGSRg64~rC3?)h7Vc}XJ`3IXI>Q&@4Qnmc1+$& zoU_{4>#|M{`=MXQIS8BJ4JleUYsW!$cgETh4(~55X8o;79TJOl!*4alq?Y7>q)mOq Lq>a9Ot*`$FZBYX^ delta 51 zcmV-30L=f^Nq{i0WC6350bT(GG5{_BF|+vsa{>W7lOY5nvycVe5R=js5|exu4wKXj J5VJxflmW445Q6{! From 08ee5c95af1917681430354c47811604d3190635 Mon Sep 17 00:00:00 2001 From: Markus Szumovski Date: Mon, 22 Aug 2022 12:03:54 +0200 Subject: [PATCH 4/4] fixed module manifest tag names (removed spaces) --- .../TUN.CredentialManager.psd1 | Bin 9430 -> 9508 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/PSCredentialManager.Cmdlet/TUN.CredentialManager.psd1 b/PSCredentialManager.Cmdlet/TUN.CredentialManager.psd1 index d31446a19ef4b5b638b6d7a8bd583c17b0be529d..df52b85c7af2548b21f925478e48b9fddda8ccfd 100644 GIT binary patch delta 109 zcmccSxx{P33~}yohD?S$h7^W;hVsd)#bqb26PKF2Mx0~vCvlO^|CJ0QvAuhpK yJo%%z@?<9okx3FFAPTI8O@a@^2C2!A5CSp5>O?2+kl>lDBMef+wfTv}CvE`Bmm|{v delta 73 zcmZ4Db3mjD0&