Repository: PowerShell/PowerShell
File: src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs (C#)

Code before:
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Globalization;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Security;

using Microsoft.Win32;

namespace Microsoft.PowerShell
{
    /// <summary>
    /// ConsoleHostUserInterface implements console-mode user interface for powershell.
    /// </summary>
    internal partial
    class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInterface
    {
        /// <summary>
        /// Prompt for credentials.
        ///
        /// In future, when we have Credential object from the security team,
        /// this function will be modified to prompt using secure-path
        /// if so configured.
        /// </summary>
        /// <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>
        /// <param name="targetName">Name of the target for which creds are being collected.</param>
        /// <param name="message">Message to be displayed.</param>
        /// <param name="caption">Caption for the message.</param>
        /// <returns>PSCredential object.</returns>

        public override PSCredential PromptForCredential(
            string caption,
            string message,
            string userName,
            string targetName)
        {
            return PromptForCredential(caption,
                                         message,
                                         userName,
                                         targetName,
                                         PSCredentialTypes.Default,
                                         PSCredentialUIOptions.Default);
        }

        /// <summary>
        /// Prompt for credentials.
        /// </summary>
        /// <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>
        /// <param name="targetName">Name of the target for which creds are being collected.</param>
        /// <param name="message">Message to be displayed.</param>
        /// <param name="caption">Caption for the message.</param>
        /// <param name="allowedCredentialTypes">What type of creds can be supplied by the user.</param>
        /// <param name="options">Options that control the cred gathering UI behavior.</param>
        /// <returns>PSCredential object, or null if input was cancelled (or if reading from stdin and stdin at EOF).</returns>

        public override PSCredential PromptForCredential(
            string caption,
            string message,
            string userName,
            string targetName,
            PSCredentialTypes allowedCredentialTypes,
            PSCredentialUIOptions options)
        {
            PSCredential cred = null;
            SecureString password = null;
            string userPrompt = null;
            string passwordPrompt = null;

            if (!string.IsNullOrEmpty(caption))
            {
                // Should be a skin lookup

                WriteLineToConsole();
                WriteLineToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption));
            }

            if (!string.IsNullOrEmpty(message))
            {
                WriteLineToConsole(WrapToCurrentWindowWidth(message));
            }

            if (string.IsNullOrEmpty(userName))
            {
                userPrompt = ConsoleHostUserInterfaceSecurityResources.PromptForCredential_User;

                //
                // need to prompt for user name first
                //
                do
                {
                    WriteToConsole(userPrompt, true);
                    userName = ReadLine();
                    if (userName == null)
                    {
                        return null;
                    }
                }
                while (userName.Length == 0);
            }

            passwordPrompt = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_Password, userName
            );

            //
            // now, prompt for the password
            //
            WriteToConsole(passwordPrompt, true);
            password = ReadLineAsSecureString();
            if (password == null)
            {
                return null;
            }

            WriteLineToConsole();

            cred = new PSCredential(userName, password);

            return cred;
        }
    }
}



Review comment (performance):
It is not clear to me that doing multiple PInvoke reads is that performant, or that we really need to worry about performance at this level.  For readability and maintainability I am inclined to simply copy the byte array and compare bytes (zero out array in finally block).  Also note that this loop can be exited as soon as one byte mismatch is found.

Code after:

        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);
                }
            }