Skip to content

Commit a6bd47f

Browse files
authored
Add password confirmation in Get-Credential (#12782)
* Add parameter -ReEnterPassword to prompt user to re-enter the password for confirmation * Add new public overload function PromptForCredential
1 parent 9c396aa commit a6bd47f

File tree

11 files changed

+323
-73
lines changed

11 files changed

+323
-73
lines changed

src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public override Dictionary<string, PSObject> Prompt(string caption, string messa
4747
}
4848

4949
/// <summary>
50-
/// PromptForChoice.
50+
/// Prompt for choice.
5151
/// </summary>
5252
/// <param name="caption"></param>
5353
/// <param name="message"></param>
@@ -60,7 +60,7 @@ public override int PromptForChoice(string caption, string message, Collection<C
6060
}
6161

6262
/// <summary>
63-
/// PromptForCredential.
63+
/// Prompt for credential.
6464
/// </summary>
6565
/// <param name="caption"></param>
6666
/// <param name="message"></param>
@@ -73,7 +73,7 @@ public override PSCredential PromptForCredential(string caption, string message,
7373
}
7474

7575
/// <summary>
76-
/// PromptForCredential.
76+
/// Prompt for credential.
7777
/// </summary>
7878
/// <param name="caption"></param>
7979
/// <param name="message"></param>
@@ -88,7 +88,23 @@ public override PSCredential PromptForCredential(string caption, string message,
8888
}
8989

9090
/// <summary>
91-
/// ReadLine.
91+
/// Prompt for credential.
92+
/// </summary>
93+
/// <param name="caption"></param>
94+
/// <param name="message"></param>
95+
/// <param name="userName"></param>
96+
/// <param name="confirmPassword"></param>
97+
/// <param name="targetName"></param>
98+
/// <param name="allowedCredentialTypes"></param>
99+
/// <param name="options"></param>
100+
/// <returns></returns>
101+
public override PSCredential PromptForCredential(string caption, string message, string userName, bool confirmPassword, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
102+
{
103+
throw new PSNotImplementedException();
104+
}
105+
106+
/// <summary>
107+
/// Read line.
92108
/// </summary>
93109
/// <returns></returns>
94110
public override string ReadLine()

src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,10 @@ out object convertedObj
303303
PSCredential credential = null;
304304
credential =
305305
PromptForCredential(
306-
null, // caption already written
307-
null, // message already written
308-
null,
309-
string.Empty);
306+
caption: null, // caption already written
307+
message: null, // message already written
308+
userName: null,
309+
targetName: string.Empty);
310310
convertedObj = credential;
311311
cancelInput = (convertedObj == null);
312312
if ((credential != null) && (credential.Password.Length == 0) && listInput)

src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs

Lines changed: 125 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
using System;
55
using System.Globalization;
6+
using System.Linq;
67
using System.Management.Automation;
78
using System.Management.Automation.Internal;
9+
using System.Runtime.InteropServices;
810
using System.Security;
9-
1011
using Microsoft.Win32;
1112

1213
namespace Microsoft.PowerShell
@@ -24,21 +25,17 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt
2425
/// this function will be modified to prompt using secure-path
2526
/// if so configured.
2627
/// </summary>
27-
/// <param name="userName">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.</param>
28-
/// <param name="targetName">Name of the target for which creds are being collected.</param>
29-
/// <param name="message">Message to be displayed.</param>
3028
/// <param name="caption">Caption for the message.</param>
29+
/// <param name="message">Message to be displayed.</param>
30+
/// <param name="userName">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.</param>
31+
/// <param name="targetName">Name of the target for which credentials are being collected.</param>
3132
/// <returns>PSCredential object.</returns>
32-
33-
public override PSCredential PromptForCredential(
34-
string caption,
35-
string message,
36-
string userName,
37-
string targetName)
33+
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
3834
{
3935
return PromptForCredential(caption,
4036
message,
4137
userName,
38+
confirmPassword: false,
4239
targetName,
4340
PSCredentialTypes.Default,
4441
PSCredentialUIOptions.Default);
@@ -47,31 +44,62 @@ public override PSCredential PromptForCredential(
4744
/// <summary>
4845
/// Prompt for credentials.
4946
/// </summary>
50-
/// <param name="userName">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.</param>
51-
/// <param name="targetName">Name of the target for which creds are being collected.</param>
52-
/// <param name="message">Message to be displayed.</param>
5347
/// <param name="caption">Caption for the message.</param>
54-
/// <param name="allowedCredentialTypes">What type of creds can be supplied by the user.</param>
55-
/// <param name="options">Options that control the cred gathering UI behavior.</param>
48+
/// <param name="message">Message to be displayed.</param>
49+
/// <param name="userName">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.</param>
50+
/// <param name="targetName">Name of the target for which credentials are being collected.</param>
51+
/// <param name="allowedCredentialTypes">What type of credentials can be supplied by the user.</param>
52+
/// <param name="options">Options that control the credential gathering UI behavior.</param>
5653
/// <returns>PSCredential object, or null if input was cancelled (or if reading from stdin and stdin at EOF).</returns>
54+
public override PSCredential PromptForCredential(
55+
string caption,
56+
string message,
57+
string userName,
58+
string targetName,
59+
PSCredentialTypes allowedCredentialTypes,
60+
PSCredentialUIOptions options)
61+
{
62+
return PromptForCredential(
63+
caption,
64+
message,
65+
userName,
66+
confirmPassword: false,
67+
targetName,
68+
allowedCredentialTypes,
69+
options);
70+
}
5771

72+
/// <summary>
73+
/// Prompt for credentials.
74+
/// </summary>
75+
/// <param name="caption">Caption for the message.</param>
76+
/// <param name="message">Message to be displayed.</param>
77+
/// <param name="userName">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.</param>
78+
/// <param name="confirmPassword">Prompts user to re-enter the password for confirmation.</param>
79+
/// <param name="targetName">Name of the target for which credentials are being collected.</param>
80+
/// <param name="allowedCredentialTypes">What type of credentials can be supplied by the user.</param>
81+
/// <param name="options">Options that control the credential gathering UI behavior.</param>
82+
/// <returns>PSCredential object, or null if input was cancelled (or if reading from stdin and stdin at EOF).</returns>
5883
public override PSCredential PromptForCredential(
5984
string caption,
6085
string message,
6186
string userName,
87+
bool confirmPassword,
6288
string targetName,
6389
PSCredentialTypes allowedCredentialTypes,
6490
PSCredentialUIOptions options)
6591
{
6692
PSCredential cred = null;
6793
SecureString password = null;
94+
SecureString reenterPassword = null;
6895
string userPrompt = null;
6996
string passwordPrompt = null;
97+
string confirmPasswordPrompt = null;
98+
string passwordMismatch = null;
7099

71100
if (!string.IsNullOrEmpty(caption))
72101
{
73102
// Should be a skin lookup
74-
75103
WriteLineToConsole();
76104
WriteLineToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption));
77105
}
@@ -85,9 +113,7 @@ public override PSCredential PromptForCredential(
85113
{
86114
userPrompt = ConsoleHostUserInterfaceSecurityResources.PromptForCredential_User;
87115

88-
//
89116
// need to prompt for user name first
90-
//
91117
do
92118
{
93119
WriteToConsole(userPrompt, true);
@@ -100,25 +126,95 @@ public override PSCredential PromptForCredential(
100126
while (userName.Length == 0);
101127
}
102128

103-
passwordPrompt = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_Password, userName
104-
);
129+
passwordPrompt = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_Password, userName);
105130

106-
//
107131
// now, prompt for the password
108-
//
109-
WriteToConsole(passwordPrompt, true);
110-
password = ReadLineAsSecureString();
111-
if (password == null)
132+
do
112133
{
113-
return null;
134+
WriteToConsole(passwordPrompt, true);
135+
password = ReadLineAsSecureString();
136+
if (password == null)
137+
{
138+
return null;
139+
}
114140
}
141+
while (password.Length == 0);
115142

116-
WriteLineToConsole();
143+
if (confirmPassword)
144+
{
145+
confirmPasswordPrompt = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_ReenterPassword, userName);
146+
passwordMismatch = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_PasswordMismatch);
117147

118-
cred = new PSCredential(userName, password);
148+
// now, prompt to re-enter the password.
149+
WriteToConsole(confirmPasswordPrompt, true);
150+
reenterPassword = ReadLineAsSecureString();
151+
if (reenterPassword == null)
152+
{
153+
return null;
154+
}
119155

156+
if (!SecureStringEquals(password, reenterPassword))
157+
{
158+
WriteToConsole(ConsoleColor.Red, ConsoleColor.Black, passwordMismatch, false);
159+
return null;
160+
}
161+
}
162+
163+
WriteLineToConsole();
164+
cred = new PSCredential(userName, password);
120165
return cred;
121166
}
167+
168+
private static bool SecureStringEquals(SecureString password, SecureString confirmPassword)
169+
{
170+
if (password.Length != confirmPassword.Length)
171+
{
172+
return false;
173+
}
174+
175+
IntPtr pwd_ptr = IntPtr.Zero;
176+
IntPtr confirmPwd_ptr = IntPtr.Zero;
177+
try
178+
{
179+
pwd_ptr = Marshal.SecureStringToBSTR(password);
180+
if (pwd_ptr == IntPtr.Zero)
181+
{
182+
return false;
183+
}
184+
185+
confirmPwd_ptr = Marshal.SecureStringToBSTR(confirmPassword);
186+
if (confirmPwd_ptr == IntPtr.Zero)
187+
{
188+
return false;
189+
}
190+
191+
int pwdLength = Marshal.ReadInt32(pwd_ptr, -4);
192+
int equal = 0;
193+
for (int i = 0; i < pwdLength; i++)
194+
{
195+
byte c1 = Marshal.ReadByte(pwd_ptr, i);
196+
byte c2 = Marshal.ReadByte(confirmPwd_ptr, i);
197+
equal = c1 ^ c2;
198+
if (equal != 0)
199+
{
200+
return false;
201+
}
202+
}
203+
204+
return true;
205+
}
206+
finally
207+
{
208+
if (pwd_ptr != IntPtr.Zero)
209+
{
210+
Marshal.ZeroFreeBSTR(pwd_ptr);
211+
}
212+
213+
if (confirmPwd_ptr != IntPtr.Zero)
214+
{
215+
Marshal.ZeroFreeBSTR(confirmPwd_ptr);
216+
}
217+
}
218+
}
122219
}
123220
}
124-

src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostUserInterfaceSecurityResources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,10 @@
123123
<data name="PromptForCredential_Password" xml:space="preserve">
124124
<value>Password for user {0}: </value>
125125
</data>
126+
<data name="PromptForCredential_ReenterPassword" xml:space="preserve">
127+
<value>Re-enter password for user {0}:</value>
128+
</data>
129+
<data name="PromptForCredential_PasswordMismatch" xml:space="preserve">
130+
<value>Passwords do not match.</value>
131+
</data>
126132
</root>

src/Microsoft.PowerShell.Security/security/CredentialCommands.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ public string Title
7979

8080
private string _title = UtilsStrings.PromptForCredential_DefaultCaption;
8181

82+
/// <summary>
83+
/// Gets or sets the confirm password prompt.
84+
/// </summary>
85+
[Parameter(ParameterSetName = messageSet)]
86+
public SwitchParameter ConfirmPassword { get; set; }
87+
8288
/// <summary>
8389
/// Initializes a new instance of the GetCredentialCommand
8490
/// class.
@@ -100,7 +106,14 @@ protected override void BeginProcessing()
100106

101107
try
102108
{
103-
Credential = this.Host.UI.PromptForCredential(_title, _message, _userName, string.Empty);
109+
Credential = this.Host.UI.PromptForCredential(
110+
_title,
111+
_message,
112+
_userName,
113+
ConfirmPassword,
114+
string.Empty,
115+
PSCredentialTypes.Default,
116+
PSCredentialUIOptions.Default);
104117
}
105118
catch (ArgumentException exception)
106119
{

0 commit comments

Comments
 (0)