mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
405 lines
13 KiB
C#
405 lines
13 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
#pragma warning disable 1634, 1691
|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Net;
|
|
using System.Security;
|
|
using System.Runtime.Serialization;
|
|
using System.Security.Cryptography;
|
|
using Microsoft.PowerShell;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// Defines the valid types of PSCredentials. Used by PromptForCredential calls.
|
|
/// </summary>
|
|
[Flags]
|
|
public enum PSCredentialTypes
|
|
{
|
|
/// <summary>
|
|
/// Generic credentials.
|
|
/// </summary>
|
|
Generic = 1,
|
|
|
|
/// <summary>
|
|
/// Credentials valid for a domain.
|
|
/// </summary>
|
|
Domain = 2,
|
|
|
|
/// <summary>
|
|
/// Default credentials.
|
|
/// </summary>
|
|
Default = Generic | Domain
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the options available when prompting for credentials. Used
|
|
/// by PromptForCredential calls.
|
|
/// </summary>
|
|
[Flags]
|
|
public enum PSCredentialUIOptions
|
|
{
|
|
/// <summary>
|
|
/// Validates the username, but not its existence
|
|
/// or correctness.
|
|
/// </summary>
|
|
Default = ValidateUserNameSyntax,
|
|
|
|
/// <summary>
|
|
/// Performs no validation.
|
|
/// </summary>
|
|
None = 0,
|
|
|
|
/// <summary>
|
|
/// Validates the username, but not its existence.
|
|
/// or correctness.
|
|
/// </summary>
|
|
ValidateUserNameSyntax,
|
|
|
|
/// <summary>
|
|
/// Always prompt, even if a persisted credential was available.
|
|
/// </summary>
|
|
AlwaysPrompt,
|
|
|
|
/// <summary>
|
|
/// Username is read-only, and the user may not modify it.
|
|
/// </summary>
|
|
ReadOnlyUserName
|
|
}
|
|
|
|
/// <summary>
|
|
/// Declare a delegate which returns the encryption key and initialization vector for symmetric encryption algorithm.
|
|
/// </summary>
|
|
/// <param name="context">The streaming context, which contains the serialization context.</param>
|
|
/// <param name="key">Symmetric encryption key.</param>
|
|
/// <param name="iv">Symmetric encryption initialization vector.</param>
|
|
/// <returns></returns>
|
|
public delegate bool GetSymmetricEncryptionKey(StreamingContext context, out byte[] key, out byte[] iv);
|
|
|
|
/// <summary>
|
|
/// Offers a centralized way to manage usernames, passwords, and
|
|
/// credentials.
|
|
/// </summary>
|
|
[Serializable]
|
|
public sealed class PSCredential : ISerializable
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets a delegate which returns the encryption key and initialization vector for symmetric encryption algorithm.
|
|
/// </summary>
|
|
public static GetSymmetricEncryptionKey GetSymmetricEncryptionKeyDelegate
|
|
{
|
|
get
|
|
{
|
|
return s_delegate;
|
|
}
|
|
|
|
set
|
|
{
|
|
s_delegate = value;
|
|
}
|
|
}
|
|
|
|
private static GetSymmetricEncryptionKey s_delegate = null;
|
|
|
|
/// <summary>
|
|
/// GetObjectData.
|
|
/// </summary>
|
|
/// <param name="info"></param>
|
|
/// <param name="context"></param>
|
|
public void GetObjectData(SerializationInfo info, StreamingContext context)
|
|
{
|
|
if (info == null)
|
|
return;
|
|
|
|
// serialize the secure string
|
|
string safePassword = string.Empty;
|
|
|
|
if (_password != null && _password.Length > 0)
|
|
{
|
|
byte[] key;
|
|
byte[] iv;
|
|
if (s_delegate != null && s_delegate(context, out key, out iv))
|
|
{
|
|
safePassword = SecureStringHelper.Encrypt(_password, key, iv).EncryptedData;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
safePassword = SecureStringHelper.Protect(_password);
|
|
}
|
|
catch (CryptographicException cryptographicException)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(cryptographicException, Credential.CredentialDisallowed);
|
|
}
|
|
}
|
|
}
|
|
|
|
info.AddValue("UserName", _userName);
|
|
info.AddValue("Password", safePassword);
|
|
}
|
|
|
|
/// <summary>
|
|
/// PSCredential.
|
|
/// </summary>
|
|
/// <param name="info"></param>
|
|
/// <param name="context"></param>
|
|
private PSCredential(SerializationInfo info, StreamingContext context)
|
|
{
|
|
if (info == null)
|
|
return;
|
|
|
|
_userName = (string)info.GetValue("UserName", typeof(string));
|
|
|
|
// deserialize to secure string
|
|
string safePassword = (string)info.GetValue("Password", typeof(string));
|
|
if (safePassword == string.Empty)
|
|
{
|
|
_password = new SecureString();
|
|
}
|
|
else
|
|
{
|
|
byte[] key;
|
|
byte[] iv;
|
|
if (s_delegate != null && s_delegate(context, out key, out iv))
|
|
{
|
|
_password = SecureStringHelper.Decrypt(safePassword, key, iv);
|
|
}
|
|
else
|
|
{
|
|
_password = SecureStringHelper.Unprotect(safePassword);
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly string _userName;
|
|
private readonly SecureString _password;
|
|
|
|
/// <summary>
|
|
/// User's name.
|
|
/// </summary>
|
|
public string UserName
|
|
{
|
|
get { return _userName; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// User's password.
|
|
/// </summary>
|
|
public SecureString Password
|
|
{
|
|
get { return _password; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the PSCredential class with a
|
|
/// username and password.
|
|
/// </summary>
|
|
/// <param name="userName">User's name.</param>
|
|
/// <param name="password">User's password.</param>
|
|
public PSCredential(string userName, SecureString password)
|
|
{
|
|
Utils.CheckArgForNullOrEmpty(userName, "userName");
|
|
Utils.CheckArgForNull(password, "password");
|
|
|
|
_userName = userName;
|
|
_password = password;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the PSCredential class with a
|
|
/// username and password from PSObject.
|
|
/// </summary>
|
|
/// <param name="pso"></param>
|
|
public PSCredential(PSObject pso)
|
|
{
|
|
if (pso == null)
|
|
throw PSTraceSource.NewArgumentNullException(nameof(pso));
|
|
|
|
if (pso.Properties["UserName"] != null)
|
|
{
|
|
_userName = (string)pso.Properties["UserName"].Value;
|
|
|
|
if (pso.Properties["Password"] != null)
|
|
_password = (SecureString)pso.Properties["Password"].Value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the PSCredential class.
|
|
/// </summary>
|
|
private PSCredential()
|
|
{
|
|
}
|
|
|
|
private NetworkCredential _netCred;
|
|
|
|
/// <summary>
|
|
/// Returns an equivalent NetworkCredential object for this
|
|
/// PSCredential.
|
|
///
|
|
/// A null is returned if
|
|
/// -- current object has not been initialized
|
|
/// -- current creds are not compatible with NetworkCredential
|
|
/// (such as smart card creds or cert creds)
|
|
/// </summary>
|
|
/// <returns>
|
|
/// null if the current object has not been initialized.
|
|
/// null if the current credentials are incompatible with
|
|
/// a NetworkCredential -- such as smart card credentials.
|
|
/// the appropriate network credential for this PSCredential otherwise.
|
|
/// </returns>
|
|
public NetworkCredential GetNetworkCredential()
|
|
{
|
|
if (_netCred == null)
|
|
{
|
|
string user = null;
|
|
string domain = null;
|
|
|
|
if (IsValidUserName(_userName, out user, out domain))
|
|
{
|
|
_netCred = new NetworkCredential(user, _password, domain);
|
|
}
|
|
}
|
|
|
|
return _netCred;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides an explicit cast to get a NetworkCredential
|
|
/// from this PSCredential.
|
|
/// </summary>
|
|
/// <param name="credential">PSCredential to convert.</param>
|
|
/// <returns>
|
|
/// null if the current object has not been initialized.
|
|
/// null if the current credentials are incompatible with
|
|
/// a NetworkCredential -- such as smart card credentials.
|
|
/// the appropriate network credential for this PSCredential otherwise.
|
|
/// </returns>
|
|
public static explicit operator NetworkCredential(PSCredential credential)
|
|
{
|
|
#pragma warning disable 56506
|
|
|
|
if (credential == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("credential");
|
|
}
|
|
|
|
return credential.GetNetworkCredential();
|
|
|
|
#pragma warning restore 56506
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an empty PSCredential. This is an PSCredential with both UserName
|
|
/// and Password initialized to null.
|
|
/// </summary>
|
|
public static PSCredential Empty
|
|
{
|
|
get
|
|
{
|
|
return s_empty;
|
|
}
|
|
}
|
|
|
|
private static readonly PSCredential s_empty = new PSCredential();
|
|
|
|
/// <summary>
|
|
/// Parse a string that represents a fully qualified username
|
|
/// to verify that it is syntactically valid. We only support
|
|
/// two formats:
|
|
/// -- domain\user
|
|
/// -- user@domain
|
|
///
|
|
/// for any other format, we simply treat the entire string
|
|
/// as user name and set domain name to "".
|
|
/// </summary>
|
|
private static bool IsValidUserName(string input,
|
|
out string user,
|
|
out string domain)
|
|
{
|
|
if (string.IsNullOrEmpty(input))
|
|
{
|
|
user = domain = null;
|
|
return false;
|
|
}
|
|
|
|
SplitUserDomain(input, out user, out domain);
|
|
|
|
if ((user == null) ||
|
|
(domain == null) ||
|
|
(user.Length == 0))
|
|
{
|
|
// UserName is the public property of Credential object. Use this as
|
|
// parameter name in error
|
|
// See bug NTRAID#Windows OS Bugs-1106386-2005/03/25-hiteshr
|
|
throw PSTraceSource.NewArgumentException("UserName", Credential.InvalidUserNameFormat);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Split a given string into its user and domain
|
|
/// components. Supported formats are:
|
|
/// -- domain\user
|
|
/// -- user@domain
|
|
///
|
|
/// With any other format, the entire input is treated as user
|
|
/// name and domain is set to "".
|
|
///
|
|
/// In any case, the function does not check if the split string
|
|
/// are really valid as user or domain names.
|
|
/// </summary>
|
|
private static void SplitUserDomain(string input,
|
|
out string user,
|
|
out string domain)
|
|
{
|
|
int i = 0;
|
|
user = null;
|
|
domain = null;
|
|
|
|
if ((i = input.IndexOf('\\')) >= 0)
|
|
{
|
|
user = input.Substring(i + 1);
|
|
domain = input.Substring(0, i);
|
|
return;
|
|
}
|
|
|
|
// In V1 and V2, we had a bug where email addresses (i.e. foo@bar.com)
|
|
// were being split into Username=Foo, Domain=bar.com.
|
|
//
|
|
// This was breaking apps (i.e.: Exchange), so we need to make
|
|
// Username = foo@bar.com if the domain has a dot in it (since
|
|
// domains can't have dots).
|
|
//
|
|
// HOWEVER, there was a workaround for this bug in v1 and v2, where the
|
|
// cred could be entered as "foo@bar.com@bar.com" - making:
|
|
// Username = foo@bar.com, Domain = bar.com
|
|
//
|
|
// We need to keep the behaviour in this case.
|
|
|
|
i = input.LastIndexOf('@');
|
|
|
|
if (
|
|
(i >= 0) &&
|
|
(
|
|
(input.LastIndexOf('.') < i) ||
|
|
(input.IndexOf('@') != i)
|
|
)
|
|
)
|
|
{
|
|
domain = input.Substring(i + 1);
|
|
user = input.Substring(0, i);
|
|
}
|
|
else
|
|
{
|
|
user = input;
|
|
domain = string.Empty;
|
|
}
|
|
}
|
|
}
|
|
}
|