namespace SecureCredentialsLibrary
{
/// Encapsulates dialog functionality from the Credential Management API.
public sealed class CredentialsDialog
{
/// The only valid bitmap height (in pixels) of a user-defined banner.
private const int ValidBannerHeight = 60;
/// The only valid bitmap width (in pixels) of a user-defined banner.
private const int ValidBannerWidth = 320;
/// Initializes a new instance of the class
/// with the specified target.
/// The name of the target for the credentials, typically a server name.
public CredentialsDialog(string target) : this(target, null)
{ }
/// Initializes a new instance of the class
/// with the specified target and caption.
/// The name of the target for the credentials, typically a server name.
/// The caption of the dialog (null will cause a system default title to be used).
public CredentialsDialog(string target, string caption) : this(target, caption, null)
{ }
/// Initializes a new instance of the class
/// with the specified target, caption and message.
/// The name of the target for the credentials, typically a server name.
/// The caption of the dialog (null will cause a system default title to be used).
/// The message of the dialog (null will cause a system default message to be used).
public CredentialsDialog(string target, string caption, string message) : this(target, caption, message, null)
{ }
/// Initializes a new instance of the class
/// with the specified target, caption, message and banner.
/// The name of the target for the credentials, typically a server name.
/// The caption of the dialog (null will cause a system default title to be used).
/// The message of the dialog (null will cause a system default message to be used).
/// The image to display on the dialog (null will cause a system default image to be used).
public CredentialsDialog(string target, string caption, string message, Image banner)
{
this.Target = target;
this.Caption = caption;
this.Message = message;
this.Banner = banner;
}
private bool _alwaysDisplay = false;
///
/// Gets or sets if the dialog will be shown even if the credentials
/// can be returned from an existing credential in the credential manager.
///
public bool AlwaysDisplay
{
get
{
return _alwaysDisplay;
}
set
{
_alwaysDisplay = value;
}
}
private bool _excludeCertificates = true;
/// Gets or sets if the dialog is populated with name/password only.
public bool ExcludeCertificates
{
get
{
return _excludeCertificates;
}
set
{
_excludeCertificates = value;
}
}
private bool _persist = true;
/// Gets or sets if the credentials are to be persisted in the credential manager.
public bool Persist
{
get
{
return _persist;
}
set
{
_persist = value;
}
}
private bool _keepName = false;
/// Gets or sets if the name is read-only.
public bool KeepName
{
get
{
return _keepName;
}
set
{
_keepName = value;
}
}
private string _name = String.Empty;
/// Gets or sets the name for the credentials.
public string Name
{
get
{
return _name;
}
set
{
if (value != null)
{
if (value.Length > CREDUI.MAX_USERNAME_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The name has a maximum length of {0} characters.",
CREDUI.MAX_USERNAME_LENGTH);
throw new ArgumentException(message, "Name");
}
}
_name = value;
}
}
private string _password = String.Empty;
/// Gets or sets the password for the credentials.
public string Password
{
get
{
return _password;
}
set
{
if (value != null)
{
if (value.Length > CREDUI.MAX_PASSWORD_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The password has a maximum length of {0} characters.",
CREDUI.MAX_PASSWORD_LENGTH);
throw new ArgumentException(message, "Password");
}
}
_password = value;
}
}
private bool _saveChecked = false;
/// Gets or sets if the save checkbox status.
public bool SaveChecked
{
get
{
return _saveChecked;
}
set
{
_saveChecked = value;
}
}
private bool _saveDisplayed = true;
/// Gets or sets if the save checkbox is displayed.
/// This value only has effect if Persist is true.
public bool SaveDisplayed
{
get
{
return _saveDisplayed;
}
set
{
_saveDisplayed = value;
}
}
private string _target = String.Empty;
/// Gets or sets the name of the target for the credentials, typically a server name.
public string Target
{
get
{
return _target;
}
set
{
if (value == null)
{
throw new ArgumentException("The target cannot be a null value.", "Target");
}
else if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The target has a maximum length of {0} characters.",
CREDUI.MAX_GENERIC_TARGET_LENGTH);
throw new ArgumentException(message, "Target");
}
_target = value;
}
}
private string _caption = String.Empty;
/// Gets or sets the caption of the dialog.
/// A null value will cause a system default caption to be used.
public string Caption
{
get
{
return _caption;
}
set
{
if (value != null)
{
if (value.Length > CREDUI.MAX_CAPTION_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The caption has a maximum length of {0} characters.",
CREDUI.MAX_CAPTION_LENGTH);
throw new ArgumentException(message, "Caption");
}
}
_caption = value;
}
}
private string _message = String.Empty;
/// Gets or sets the message of the dialog.
/// A null value will cause a system default message to be used.
public string Message
{
get
{
return _message;
}
set
{
if (value != null)
{
if (value.Length > CREDUI.MAX_MESSAGE_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The message has a maximum length of {0} characters.",
CREDUI.MAX_MESSAGE_LENGTH);
throw new ArgumentException(message, "Message");
}
}
_message = value;
}
}
private Image _banner = null;
/// Gets or sets the image to display on the dialog.
/// A null value will cause a system default image to be used.
public Image Banner
{
get
{
return _banner;
}
set
{
if (value != null)
{
if (value.Width != ValidBannerWidth)
{
throw new ArgumentException("The banner image width must be 320 pixels.", "Banner");
}
if (value.Height != ValidBannerHeight)
{
throw new ArgumentException("The banner image height must be 60 pixels.", "Banner");
}
}
_banner = value;
}
}
/// Shows the credentials dialog.
/// Returns a DialogResult indicating the user action.
public DialogResult Show()
{
return Show(null, this.Name, this.Password, this.SaveChecked);
}
/// Shows the credentials dialog with the specified save checkbox status.
/// True if the save checkbox is checked.
/// Returns a DialogResult indicating the user action.
public DialogResult Show(bool saveChecked)
{
return Show(null, this.Name, this.Password, saveChecked);
}
/// Shows the credentials dialog with the specified name.
/// The name for the credentials.
/// Returns a DialogResult indicating the user action.
public DialogResult Show(string name)
{
return Show(null, name, this.Password, this.SaveChecked);
}
/// Shows the credentials dialog with the specified name and password.
/// The name for the credentials.
/// The password for the credentials.
/// Returns a DialogResult indicating the user action.
public DialogResult Show(string name, string password)
{
return Show(null, name, password, this.SaveChecked);
}
/// Shows the credentials dialog with the specified name, password and save checkbox status.
/// The name for the credentials.
/// The password for the credentials.
/// True if the save checkbox is checked.
/// Returns a DialogResult indicating the user action.
public DialogResult Show(string name, string password, bool saveChecked)
{
return Show(null, name, password, saveChecked);
}
/// Shows the credentials dialog with the specified owner.
/// The System.Windows.Forms.IWin32Window the dialog will display in front of.
/// Returns a DialogResult indicating the user action.
public DialogResult Show(IWin32Window owner)
{
return Show(owner, this.Name, this.Password, this.SaveChecked);
}
/// Shows the credentials dialog with the specified owner and save checkbox status.
/// The System.Windows.Forms.IWin32Window the dialog will display in front of.
/// True if the save checkbox is checked.
/// Returns a DialogResult indicating the user action.
public DialogResult Show(IWin32Window owner, bool saveChecked)
{
return Show(owner, this.Name, this.Password, saveChecked);
}
/// Shows the credentials dialog with the specified owner, name and password.
/// The System.Windows.Forms.IWin32Window the dialog will display in front of.
/// The name for the credentials.
/// The password for the credentials.
/// Returns a DialogResult indicating the user action.
public DialogResult Show(IWin32Window owner, string name, string password)
{
return Show(owner, name, password, this.SaveChecked);
}
/// Shows the credentials dialog with the specified owner, name, password and save checkbox status.
/// The System.Windows.Forms.IWin32Window the dialog will display in front of.
/// The name for the credentials.
/// The password for the credentials.
/// True if the save checkbox is checked.
/// Returns a DialogResult indicating the user action.
public DialogResult Show(IWin32Window owner, string name, string password, bool saveChecked)
{
if (Environment.OSVersion.Version.Major < 5)
{
throw new ApplicationException("The Credential Management API requires Windows XP / Windows Server 2003 or later.");
}
this.Name = name;
this.Password = password;
this.SaveChecked = saveChecked;
return ShowDialog(owner);
}
/// Confirmation action to be applied.
/// True if the credentials should be persisted.
public void Confirm(bool value)
{
switch (CREDUI.ConfirmCredentials(this.Target, value))
{
case CREDUI.ReturnCodes.NO_ERROR:
break;
case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER:
// for some reason, this is encountered when credentials are overwritten
break;
default:
throw new ApplicationException("Credential confirmation failed.");
break;
}
}
/// Returns a DialogResult indicating the user action.
/// The System.Windows.Forms.IWin32Window the dialog will display in front of.
///
/// Sets the name, password and SaveChecked accessors to the state of the dialog as it was dismissed by the user.
///
private DialogResult ShowDialog(IWin32Window owner)
{
// set the api call parameters
StringBuilder name = new StringBuilder(CREDUI.MAX_USERNAME_LENGTH);
name.Append(this.Name);
StringBuilder password = new StringBuilder(CREDUI.MAX_PASSWORD_LENGTH);
password.Append(this.Password);
int saveChecked = Convert.ToInt32(this.SaveChecked);
CREDUI.INFO info = GetInfo(owner);
CREDUI.FLAGS flags = GetFlags();
// make the api call
CREDUI.ReturnCodes code = CREDUI.PromptForCredentials(
ref info,
this.Target,
IntPtr.Zero, 0,
name, CREDUI.MAX_USERNAME_LENGTH,
password, CREDUI.MAX_PASSWORD_LENGTH,
ref saveChecked,
flags
);
// clean up resources
if (this.Banner != null) GDI32.DeleteObject(info.hbmBanner);
// set the accessors from the api call parameters
this.Name = name.ToString();
this.Password = password.ToString();
this.SaveChecked = Convert.ToBoolean(saveChecked);
return GetDialogResult(code);
}
/// Returns the info structure for dialog display settings.
/// The System.Windows.Forms.IWin32Window the dialog will display in front of.
private CREDUI.INFO GetInfo(IWin32Window owner)
{
CREDUI.INFO info = new CREDUI.INFO();
if (owner != null) info.hwndParent = owner.Handle;
info.pszCaptionText = this.Caption;
info.pszMessageText = this.Message;
if (this.Banner != null)
{
info.hbmBanner = new Bitmap(this.Banner, ValidBannerWidth, ValidBannerHeight).GetHbitmap();
}
info.cbSize = Marshal.SizeOf(info);
return info;
}
/// Returns the flags for dialog display options.
private CREDUI.FLAGS GetFlags()
{
CREDUI.FLAGS flags = CREDUI.FLAGS.GENERIC_CREDENTIALS;
// grrrr... can't seem to get this to work...
// if (incorrectPassword) flags = flags | CredUI.CREDUI_FLAGS.INCORRECT_PASSWORD;
if (this.AlwaysDisplay) flags = flags | CREDUI.FLAGS.ALWAYS_SHOW_UI;
if (this.ExcludeCertificates) flags = flags | CREDUI.FLAGS.EXCLUDE_CERTIFICATES;
if (this.Persist)
{
flags = flags | CREDUI.FLAGS.EXPECT_CONFIRMATION;
if (this.SaveDisplayed) flags = flags | CREDUI.FLAGS.SHOW_SAVE_CHECK_BOX;
}
else
{
flags = flags | CREDUI.FLAGS.DO_NOT_PERSIST;
}
if (this.KeepName) flags = flags | CREDUI.FLAGS.KEEP_USERNAME;
return flags;
}
/// Returns a DialogResult from the specified code.
/// The credential return code.
private DialogResult GetDialogResult(CREDUI.ReturnCodes code)
{
DialogResult result;
switch (code)
{
case CREDUI.ReturnCodes.NO_ERROR:
result = DialogResult.OK;
break;
case CREDUI.ReturnCodes.ERROR_CANCELLED:
result = DialogResult.Cancel;
break;
case CREDUI.ReturnCodes.ERROR_NO_SUCH_LOGON_SESSION:
throw new ApplicationException("No such logon session.");
break;
case CREDUI.ReturnCodes.ERROR_NOT_FOUND:
throw new ApplicationException("Not found.");
break;
case CREDUI.ReturnCodes.ERROR_INVALID_ACCOUNT_NAME:
throw new ApplicationException("Invalid account name.");
break;
case CREDUI.ReturnCodes.ERROR_INSUFFICIENT_BUFFER:
throw new ApplicationException("Insufficient buffer.");
break;
case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER:
throw new ApplicationException("Invalid parameter.");
break;
case CREDUI.ReturnCodes.ERROR_INVALID_FLAGS:
throw new ApplicationException("Invalid flags.");
break;
default:
throw new ApplicationException("Unknown credential result encountered.");
break;
}
return result;
}
}
public sealed class GDI32
{
private GDI32()
{ }
[DllImport("gdi32.dll", EntryPoint="DeleteObject")]
public static extern bool DeleteObject(IntPtr hObject);
}
public sealed class CREDUI
{
private CREDUI()
{ }
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/authentication_constants.asp
public const int MAX_MESSAGE_LENGTH = 100;
public const int MAX_CAPTION_LENGTH = 100;
public const int MAX_GENERIC_TARGET_LENGTH = 100;
public const int MAX_DOMAIN_TARGET_LENGTH = 100;
public const int MAX_USERNAME_LENGTH = 100;
public const int MAX_PASSWORD_LENGTH = 100;
///
/// http://www.pinvoke.net/default.aspx/Enums.CREDUI_FLAGS
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/dpapiusercredentials.asp
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp
///
[Flags] public enum FLAGS
{
INCORRECT_PASSWORD = 0x1,
DO_NOT_PERSIST = 0x2,
REQUEST_ADMINISTRATOR = 0x4,
EXCLUDE_CERTIFICATES = 0x8,
REQUIRE_CERTIFICATE = 0x10,
SHOW_SAVE_CHECK_BOX = 0x40,
ALWAYS_SHOW_UI = 0x80,
REQUIRE_SMARTCARD = 0x100,
PASSWORD_ONLY_OK = 0x200,
VALIDATE_USERNAME = 0x400,
COMPLETE_USERNAME = 0x800,
PERSIST = 0x1000,
SERVER_CREDENTIAL = 0x4000,
EXPECT_CONFIRMATION = 0x20000,
GENERIC_CREDENTIALS = 0x40000,
USERNAME_TARGET_CREDENTIALS = 0x80000,
KEEP_USERNAME = 0x100000,
}
/// http://www.pinvoke.net/default.aspx/Enums.CredUIReturnCodes
public enum ReturnCodes
{
NO_ERROR = 0,
ERROR_INVALID_PARAMETER = 87,
ERROR_INSUFFICIENT_BUFFER = 122,
ERROR_INVALID_FLAGS = 1004,
ERROR_NOT_FOUND = 1168,
ERROR_CANCELLED = 1223,
ERROR_NO_SUCH_LOGON_SESSION = 1312,
ERROR_INVALID_ACCOUNT_NAME = 1315
}
///
/// http://www.pinvoke.net/default.aspx/Structures.CREDUI_INFO
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/credui_info.asp
///
public struct INFO
{
public int cbSize;
public IntPtr hwndParent;
[MarshalAs(UnmanagedType.LPWStr)] public string pszMessageText;
[MarshalAs(UnmanagedType.LPWStr)] public string pszCaptionText;
public IntPtr hbmBanner;
}
///
/// http://www.pinvoke.net/default.aspx/credui.CredUIPromptForCredentialsW
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp
///
[DllImport("credui", EntryPoint="CredUIPromptForCredentialsW", CharSet=CharSet.Unicode)]
public static extern ReturnCodes PromptForCredentials(
ref INFO creditUR,
string targetName,
IntPtr reserved1,
int iError,
StringBuilder userName,
int maxUserName,
StringBuilder password,
int maxPassword,
ref int iSave,
FLAGS flags
);
///
/// http://www.pinvoke.net/default.aspx/credui.CredUIConfirmCredentials
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduiconfirmcredentials.asp
///
[DllImport("credui.dll", EntryPoint="CredUIConfirmCredentialsW", CharSet=CharSet.Unicode)]
public static extern ReturnCodes ConfirmCredentials(
string targetName,
bool confirm
);
}
}