Search code examples
c#winformscontrolssecurestring

SecurePasswordTextBox preventing tab and enter keys working


I have used a modified version of the SecurePasswordTextBox to create secure input controls on my WinForms application (yes I know it's largely pointless but one of our clients has been insistent that no sensitive data should be handled as strings in memory.

However, for some reason. This appears to have broken the tab and enter keys for the form after the control has been selected. We can no longer use these to jump between controls. How would I go about fixing this? I've tried using SendKeys() but that doesn't seem to help.

The source ie below. I have modified it by adding an overriding Clear() function.

namespace SecurePasswordTextBox
{
    /// <summary>
    /// This is a TextBox implementation that uses the System.Security.SecureString as its backing
    /// store instead of standard managed string instance. At no time, is a managed string instance
    /// used to hold a component of the textual entry.
    /// It does not display any text and relies on the 'PasswordChar' character to display the amount of
    /// characters entered. If no password char is defined, then an 'asterisk' is used.
    /// </summary>
    public partial class SecureTextBox : TextBox
    {
        #region Private fields

        private bool _displayChar = false;
        SecureString _secureEntry = new SecureString();

        private TextBox _innerTextBox = new TextBox();

        #endregion

        #region Constructor 
        public SecureTextBox()
        {
            InitializeComponent();

            this.PasswordChar = '*';   // default to an asterisk
        }

        #endregion

        #region Public properties

        /// <summary>
        /// The secure string instance captured so far.
        /// This is the preferred method of accessing the string contents.
        /// </summary>
        public SecureString SecureText
        {
            get
            {
                return _secureEntry;
            }
            set
            {
                _secureEntry = value;
            }
        }

        /// <summary>
        /// Allows the consumer to retrieve this string instance as a character array. NOte that this is still
        /// visible plainly in memory and should be 'consumed' as wuickly as possible, then the contents
        /// 'zero-ed' so that they cannot be viewed.
        /// </summary>
        public char[] CharacterData
        {
            get
            {
                char[] bytes = new char[_secureEntry.Length];
                IntPtr ptr = IntPtr.Zero;

                try
                {
                    ptr = Marshal.SecureStringToBSTR(_secureEntry);
                    bytes = new char[_secureEntry.Length];
                    Marshal.Copy(ptr, bytes,0,_secureEntry.Length);
                }
                finally
                {
                    if (ptr != IntPtr.Zero)
                        Marshal.ZeroFreeBSTR(ptr);
                }

                return bytes;
            }
        }


        #endregion

        #region ProcessKeyMessage

        protected override bool  ProcessKeyMessage(ref Message m)
        {

            if (_displayChar)
            {
                return base.ProcessKeyMessage(ref m);
            }
            else
            {
                _displayChar = true;
                return true;
            }
        }

        #endregion

        #region IsInputChar

        protected override bool IsInputChar(char charCode)
        {
            int startPos = this.SelectionStart;

            bool isChar = base.IsInputChar(charCode);
            if (isChar)
            {
                int keyCode = (int)charCode;

                // If the key pressed is NOT a control/cursor type key, then add it to our instance.
                // Note: This does not catch the SHIFT key or anything like that
                if (!Char.IsControl(charCode) && !char.IsHighSurrogate(charCode) && !char.IsLowSurrogate(charCode))
                {

                    if (this.SelectionLength > 0)
                    {
                        for (int i = 0; i < this.SelectionLength; i++)
                            _secureEntry.RemoveAt(this.SelectionStart);
                    }

                    if (startPos == _secureEntry.Length)
                    {
                        _secureEntry.AppendChar(charCode);
                    }
                    else
                    {
                        _secureEntry.InsertAt(startPos, charCode);
                    }

                    this.Text = new string('*', _secureEntry.Length);


                    _displayChar = false;
                    startPos++;

                    this.SelectionStart = startPos;
                }
                else
                {
                    // We need to check what key has been pressed.

                    switch (keyCode)
                    {
                        case (int)Keys.Back:
                            if (this.SelectionLength == 0 && startPos > 0)
                            {
                                startPos--;
                                _secureEntry.RemoveAt(startPos);
                                this.Text = new string('*', _secureEntry.Length);
                                this.SelectionStart = startPos;
                            }
                            else if (this.SelectionLength > 0)
                            {
                                for (int i = 0; i < this.SelectionLength; i++)
                                    _secureEntry.RemoveAt(this.SelectionStart);
                            }
                            _displayChar = false;   // If we dont do this, we get a 'double' BACK keystroke effect

                            break;
                    }
                }
            }
            else
                _displayChar = true;

            return isChar;
        }

        #endregion

        #region IsInputKey

        protected override bool IsInputKey(Keys keyData)
        {
            bool result = true;

            // Note: This whole section is only to deal with the 'Delete' key.

            bool allowedToDelete =
                (
                     ((keyData & Keys.Delete) == Keys.Delete)
                );

            // Debugging only
            //this.Parent.Text = keyData.ToString() + " " + ((int)keyData).ToString() + " allowedToDelete = " + allowedToDelete.ToString();

            if (allowedToDelete)
            {
                if (this.SelectionLength == _secureEntry.Length)
                {
                    _secureEntry.Clear();
                }
                else if (this.SelectionLength > 0)
                {
                    for (int i = 0; i < this.SelectionLength; i++)
                        _secureEntry.RemoveAt(this.SelectionStart);

                }
                else
                {
                    if ((keyData & Keys.Delete) == Keys.Delete && this.SelectionStart < this.Text.Length)
                        _secureEntry.RemoveAt(this.SelectionStart);
                }

            }

            return result;

        }

        #endregion

    }
}

I can detect when Tab is pressed by adding:

case (int)Keys.Tab:

To the switch(KeyCode) block. However, I have no idea how to then manually invoke the "Select Next Control By Tab Index" effect that it would normally have.

Adding SelectNextControl((Control)this, true, true, true, true); to this case doesn't seem to work either.


Solution

  • In your IsInputKey override, just check for the Tab key:

    protected override bool IsInputKey(Keys keyData) {
      if (keyData == Keys.Tab) {
        return false;
      }
    
      // your code ...
    }