diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index 4c0478add3..cb3f9f01e8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -47,7 +47,7 @@ namespace Microsoft.PowerShell } /// - /// PromptForChoice. + /// Prompt for choice. /// /// /// @@ -60,7 +60,7 @@ namespace Microsoft.PowerShell } /// - /// PromptForCredential. + /// Prompt for credential. /// /// /// @@ -73,7 +73,7 @@ namespace Microsoft.PowerShell } /// - /// PromptForCredential. + /// Prompt for credential. /// /// /// @@ -88,7 +88,23 @@ namespace Microsoft.PowerShell } /// - /// ReadLine. + /// Prompt for credential. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public override PSCredential PromptForCredential(string caption, string message, string userName, bool confirmPassword, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) + { + throw new PSNotImplementedException(); + } + + /// + /// Read line. /// /// public override string ReadLine() diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs index 0afe5f987d..e1f9c9abbc 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs @@ -303,10 +303,10 @@ namespace Microsoft.PowerShell PSCredential credential = null; credential = PromptForCredential( - null, // caption already written - null, // message already written - null, - string.Empty); + caption: null, // caption already written + message: null, // message already written + userName: null, + targetName: string.Empty); convertedObj = credential; cancelInput = (convertedObj == null); if ((credential != null) && (credential.Password.Length == 0) && listInput) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs index 6926f68579..120bd38dcd 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs @@ -3,10 +3,11 @@ using System; using System.Globalization; +using System.Linq; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Runtime.InteropServices; using System.Security; - using Microsoft.Win32; namespace Microsoft.PowerShell @@ -24,21 +25,17 @@ namespace Microsoft.PowerShell /// this function will be modified to prompt using secure-path /// if so configured. /// - /// Name of the user whose creds are to be prompted for. If set to null or empty string, the function will prompt for user name first. - /// Name of the target for which creds are being collected. - /// Message to be displayed. /// Caption for the message. + /// Message to be displayed. + /// Name of the user whose credentials are to be prompted for. If set to null or empty string, the function will prompt for user name first. + /// Name of the target for which credentials are being collected. /// PSCredential object. - - public override PSCredential PromptForCredential( - string caption, - string message, - string userName, - string targetName) + public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) { return PromptForCredential(caption, message, userName, + confirmPassword: false, targetName, PSCredentialTypes.Default, PSCredentialUIOptions.Default); @@ -47,31 +44,62 @@ namespace Microsoft.PowerShell /// /// Prompt for credentials. /// - /// Name of the user whose creds are to be prompted for. If set to null or empty string, the function will prompt for user name first. - /// Name of the target for which creds are being collected. - /// Message to be displayed. /// Caption for the message. - /// What type of creds can be supplied by the user. - /// Options that control the cred gathering UI behavior. + /// Message to be displayed. + /// Name of the user whose credentials are to be prompted for. If set to null or empty string, the function will prompt for user name first. + /// Name of the target for which credentials are being collected. + /// What type of credentials can be supplied by the user. + /// Options that control the credential gathering UI behavior. /// PSCredential object, or null if input was cancelled (or if reading from stdin and stdin at EOF). + public override PSCredential PromptForCredential( + string caption, + string message, + string userName, + string targetName, + PSCredentialTypes allowedCredentialTypes, + PSCredentialUIOptions options) + { + return PromptForCredential( + caption, + message, + userName, + confirmPassword: false, + targetName, + allowedCredentialTypes, + options); + } + /// + /// Prompt for credentials. + /// + /// Caption for the message. + /// Message to be displayed. + /// Name of the user whose credentials are to be prompted for. If set to null or empty string, the function will prompt for user name first. + /// Prompts user to re-enter the password for confirmation. + /// Name of the target for which credentials are being collected. + /// What type of credentials can be supplied by the user. + /// Options that control the credential gathering UI behavior. + /// PSCredential object, or null if input was cancelled (or if reading from stdin and stdin at EOF). public override PSCredential PromptForCredential( string caption, string message, string userName, + bool confirmPassword, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) { PSCredential cred = null; SecureString password = null; + SecureString reenterPassword = null; string userPrompt = null; string passwordPrompt = null; + string confirmPasswordPrompt = null; + string passwordMismatch = null; if (!string.IsNullOrEmpty(caption)) { // Should be a skin lookup - WriteLineToConsole(); WriteLineToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption)); } @@ -85,9 +113,7 @@ namespace Microsoft.PowerShell { userPrompt = ConsoleHostUserInterfaceSecurityResources.PromptForCredential_User; - // // need to prompt for user name first - // do { WriteToConsole(userPrompt, true); @@ -100,25 +126,95 @@ namespace Microsoft.PowerShell while (userName.Length == 0); } - passwordPrompt = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_Password, userName - ); + passwordPrompt = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_Password, userName); - // // now, prompt for the password - // - WriteToConsole(passwordPrompt, true); - password = ReadLineAsSecureString(); - if (password == null) + do { - return null; + WriteToConsole(passwordPrompt, true); + password = ReadLineAsSecureString(); + if (password == null) + { + return null; + } + } + while (password.Length == 0); + + if (confirmPassword) + { + confirmPasswordPrompt = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_ReenterPassword, userName); + passwordMismatch = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_PasswordMismatch); + + // now, prompt to re-enter the password. + WriteToConsole(confirmPasswordPrompt, true); + reenterPassword = ReadLineAsSecureString(); + if (reenterPassword == null) + { + return null; + } + + if (!SecureStringEquals(password, reenterPassword)) + { + WriteToConsole(ConsoleColor.Red, ConsoleColor.Black, passwordMismatch, false); + return null; + } } WriteLineToConsole(); - cred = new PSCredential(userName, password); - return cred; } + + private static bool SecureStringEquals(SecureString password, SecureString confirmPassword) + { + if (password.Length != confirmPassword.Length) + { + return false; + } + + IntPtr pwd_ptr = IntPtr.Zero; + IntPtr confirmPwd_ptr = IntPtr.Zero; + try + { + pwd_ptr = Marshal.SecureStringToBSTR(password); + if (pwd_ptr == IntPtr.Zero) + { + return false; + } + + confirmPwd_ptr = Marshal.SecureStringToBSTR(confirmPassword); + if (confirmPwd_ptr == IntPtr.Zero) + { + return false; + } + + int pwdLength = Marshal.ReadInt32(pwd_ptr, -4); + int equal = 0; + for (int i = 0; i < pwdLength; i++) + { + byte c1 = Marshal.ReadByte(pwd_ptr, i); + byte c2 = Marshal.ReadByte(confirmPwd_ptr, i); + equal = c1 ^ c2; + if (equal != 0) + { + return false; + } + } + + return true; + } + finally + { + if (pwd_ptr != IntPtr.Zero) + { + Marshal.ZeroFreeBSTR(pwd_ptr); + } + + if (confirmPwd_ptr != IntPtr.Zero) + { + Marshal.ZeroFreeBSTR(confirmPwd_ptr); + } + } + } } } - diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostUserInterfaceSecurityResources.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostUserInterfaceSecurityResources.resx index 6c383d5052..6d705a7c7b 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostUserInterfaceSecurityResources.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostUserInterfaceSecurityResources.resx @@ -123,4 +123,10 @@ Password for user {0}: + + Re-enter password for user {0}: + + + Passwords do not match. + diff --git a/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs b/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs index cb23979c77..dddb9ee530 100644 --- a/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs @@ -79,6 +79,12 @@ namespace Microsoft.PowerShell.Commands private string _title = UtilsStrings.PromptForCredential_DefaultCaption; + /// + /// Gets or sets the confirm password prompt. + /// + [Parameter(ParameterSetName = messageSet)] + public SwitchParameter ConfirmPassword { get; set; } + /// /// Initializes a new instance of the GetCredentialCommand /// class. @@ -100,7 +106,14 @@ namespace Microsoft.PowerShell.Commands try { - Credential = this.Host.UI.PromptForCredential(_title, _message, _userName, string.Empty); + Credential = this.Host.UI.PromptForCredential( + _title, + _message, + _userName, + ConfirmPassword, + string.Empty, + PSCredentialTypes.Default, + PSCredentialUIOptions.Default); } catch (ArgumentException exception) { diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs index 7650bf6854..0c8bf71e94 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -56,6 +56,7 @@ namespace System.Management.Automation.Host /// /// /// + /// /// /// public abstract string ReadLine(); @@ -94,10 +95,12 @@ namespace System.Management.Automation.Host /// Note that credentials (a user name and password) should be gathered with /// /// + /// /// /// /// /// + /// /// /// public abstract SecureString ReadLineAsSecureString(); @@ -825,8 +828,9 @@ namespace System.Management.Automation.Host /// /// /// - /// + /// /// + /// public abstract Dictionary Prompt(string caption, string message, Collection descriptions); /// @@ -859,9 +863,12 @@ namespace System.Management.Automation.Host /// /// /// - public abstract PSCredential PromptForCredential(string caption, string message, - string userName, string targetName - ); + /// + public abstract PSCredential PromptForCredential( + string caption, + string message, + string userName, + string targetName); /// /// Prompt for credential. @@ -883,7 +890,7 @@ namespace System.Management.Automation.Host /// Types of credential can be supplied by the user. /// /// - /// Options that control the credential gathering UI behavior + /// Options that control the credential gathering UI behavior. /// /// /// User input credential. @@ -893,10 +900,57 @@ namespace System.Management.Automation.Host /// /// /// - public abstract PSCredential PromptForCredential(string caption, string message, - string userName, string targetName, PSCredentialTypes allowedCredentialTypes, - PSCredentialUIOptions options - ); + /// + public abstract PSCredential PromptForCredential( + string caption, + string message, + string userName, + string targetName, + PSCredentialTypes allowedCredentialTypes, + PSCredentialUIOptions options); + + /// + /// Prompt for credential. + /// + /// + /// Caption for the message. + /// + /// + /// Text description for the credential to be prompt. + /// + /// + /// Name of the user whose credential is to be prompted for. If set to null or empty + /// string, the function will prompt for user name first. + /// + /// + /// Prompts user to re-enter the password for confirmation. + /// + /// + /// Name of the target for which the credential is being collected. + /// + /// + /// Types of credential can be supplied by the user. + /// + /// + /// Options that control the credential gathering UI behavior. + /// + /// + /// User input credential. + /// + /// + /// + /// + /// + /// + /// + public abstract PSCredential PromptForCredential( + string caption, + string message, + string userName, + bool confirmPassword, + string targetName, + PSCredentialTypes allowedCredentialTypes, + PSCredentialUIOptions options); /// /// Presents a dialog allowing the user to choose an option from a set of options. @@ -922,6 +976,7 @@ namespace System.Management.Automation.Host /// /// /// + /// public abstract int PromptForChoice(string caption, string message, Collection choices, int defaultChoice); #endregion Dialog-oriented interaction diff --git a/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs b/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs index f5155fd7c1..86286d955e 100644 --- a/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs +++ b/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs @@ -14,38 +14,52 @@ namespace System.Management.Automation.Internal.Host /// /// See base class. /// - - public override - PSCredential - PromptForCredential - ( - string caption, - string message, - string userName, - string targetName - ) + public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) { - return PromptForCredential(caption, message, userName, - targetName, - PSCredentialTypes.Default, - PSCredentialUIOptions.Default); + return PromptForCredential( + caption, + message, + userName, + confirmPassword: false, + targetName, + PSCredentialTypes.Default, + PSCredentialUIOptions.Default); } /// /// See base class. /// - - public override - PSCredential - PromptForCredential - ( + public override PSCredential PromptForCredential( string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, - PSCredentialUIOptions options - ) + PSCredentialUIOptions options) + { + return PromptForCredential( + caption, + message, + userName, + confirmPassword: false, + targetName, + allowedCredentialTypes, + options); + } + + /// + /// See base class. + /// + public override + PSCredential + PromptForCredential( + string caption, + string message, + string userName, + bool confirmPassword, + string targetName, + PSCredentialTypes allowedCredentialTypes, + PSCredentialUIOptions options) { if (_externalUI == null) { @@ -55,7 +69,7 @@ namespace System.Management.Automation.Internal.Host PSCredential result = null; try { - result = _externalUI.PromptForCredential(caption, message, userName, targetName, allowedCredentialTypes, options); + result = _externalUI.PromptForCredential(caption, message, userName, confirmPassword, targetName, allowedCredentialTypes, options); } catch (PipelineStoppedException) { diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs index 6b69bb07b8..10abafe817 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs @@ -233,5 +233,15 @@ namespace System.Management.Automation.Remoting return _serverMethodExecutor.ExecuteMethod(RemoteHostMethodId.PromptForCredential2, new object[] { caption, message, userName, targetName, allowedCredentialTypes, options }); } + + /// + /// Prompt for credential. + /// + public override PSCredential PromptForCredential(string caption, string message, string userName, bool confirmPassword, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) + { + return _serverMethodExecutor.ExecuteMethod( + RemoteHostMethodId.PromptForCredential2, + new object[] { caption, message, userName, confirmPassword, targetName, allowedCredentialTypes, options }); + } } } diff --git a/src/System.Management.Automation/security/CredentialParameter.cs b/src/System.Management.Automation/security/CredentialParameter.cs index 0d48e4fa73..ad9463b9df 100644 --- a/src/System.Management.Automation/security/CredentialParameter.cs +++ b/src/System.Management.Automation/security/CredentialParameter.cs @@ -32,6 +32,7 @@ namespace System.Management.Automation PSCredential cred = null; string userName = null; bool shouldPrompt = false; + bool confirmPassword = false; if ((engineIntrinsics == null) || (engineIntrinsics.Host == null) || @@ -74,10 +75,13 @@ namespace System.Management.Automation prompt = CredentialAttributeStrings.CredentialAttribute_Prompt; cred = engineIntrinsics.Host.UI.PromptForCredential( - caption, - prompt, - userName, - string.Empty); + caption, + prompt, + userName, + confirmPassword, + string.Empty, + PSCredentialTypes.Default, + PSCredentialUIOptions.Default); } return cred; diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1 index cb9d0ee70e..45fa386001 100755 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1 @@ -102,4 +102,21 @@ Describe "Get-Credential Test" -Tag "CI" { $netcred.UserName | Should -Be "John" $netcred.Password | Should -Be "CredTest" } + It "Get-credential Joe -ConfirmPassword set to false"{ + $cred = $ps.AddScript("Get-Credential Joe -ConfirmPassword:`$false").Invoke() | Select-Object -First 1 + $cred | Should -BeOfType System.Management.Automation.PSCredential + $netcred = $cred.GetNetworkCredential() + $netcred.UserName | Should -Be "Joe" + $netcred.Password | Should -Be "This is a test" + $th.ui.Streams.Prompt[-1] | Should -Match "Credential:[^:]+:[^:]+" + } + It "Get-Credential with only -ConfirmPassword" { + $cred = $ps.AddScript("Get-Credential -ConfirmPassword").Invoke() | Select-Object -First 1 + $cred | Should -BeOfType System.Management.Automation.PSCredential + $netcred = $cred.GetNetworkCredential() + $netcred.UserName | Should -Be "John" + $netcred.Password | Should -Be "This is a test" + $th.ui.Streams.Prompt[-2] | Should -Match "Credential:[^:]+:[^:]+" + $th.ui.Streams.Prompt[-1] | Should -Match "Credential@[^@]+@[^@]+" + } } diff --git a/test/tools/Modules/HelpersHostCS/HelpersHostCS.psm1 b/test/tools/Modules/HelpersHostCS/HelpersHostCS.psm1 index 0408cdcc49..3be3482f98 100755 --- a/test/tools/Modules/HelpersHostCS/HelpersHostCS.psm1 +++ b/test/tools/Modules/HelpersHostCS/HelpersHostCS.psm1 @@ -10,6 +10,7 @@ using System.Globalization; using System.Collections.ObjectModel; using System.Security; using System.Collections; +using System.Runtime.InteropServices; namespace TestHost { @@ -145,18 +146,36 @@ namespace TestHost public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) { - Streams.Prompt.Add("Credential:" + caption + ":" + message); - SecureString ss = ReadLineAsSecureString(); - string userNameToUse = string.IsNullOrEmpty(userName) ? UserNameForCredential : userName; - return new PSCredential(userNameToUse, ss); + return PromptForCredential(caption, + message, + userName, + confirmPassword: false, + targetName, + PSCredentialTypes.Default, + PSCredentialUIOptions.Default); } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) + { + return PromptForCredential(caption, + message, + userName, + confirmPassword: false, + targetName, + allowedCredentialTypes, + options); + } + + public override PSCredential PromptForCredential(string caption, string message, string userName, bool confirmPassword, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) { Streams.Prompt.Add("Credential:" + caption + ":" + message); - SecureString ss = ReadLineAsSecureString(); + SecureString password = ReadLineAsSecureString(); + if(confirmPassword) + { + Streams.Prompt.Add("Credential@" + caption + "@" + message); + } string userNameToUse = string.IsNullOrEmpty(userName) ? UserNameForCredential : userName; - return new PSCredential(userNameToUse, ss); + return new PSCredential(userNameToUse, password); } public override string ReadLine()