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

391 lines
13 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using Dbg = System.Management.Automation;
namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// Defines the base class from which all SecureString commands
/// are derived.
/// </summary>
public abstract class SecureStringCommandBase : PSCmdlet
{
private SecureString _ss;
/// <summary>
/// Gets or sets the secure string to be used by the get- and set-
/// commands.
/// </summary>
protected SecureString SecureStringData
{
get { return _ss; }
set { _ss = value; }
}
//
// name of this command
//
private readonly string _commandName;
/// <summary>
/// Initializes a new instance of the SecureStringCommandBase
/// class.
/// </summary>
/// <param name="name">
/// The command name deriving from this class
/// </param>
protected SecureStringCommandBase(string name) : base()
{
_commandName = name;
}
private SecureStringCommandBase() : base() { }
}
/// <summary>
/// Defines the base class from which all SecureString import and
/// export commands are derived.
/// </summary>
public abstract class ConvertFromToSecureStringCommandBase : SecureStringCommandBase
{
/// <summary>
/// Initializes a new instance of the ConvertFromToSecureStringCommandBase
/// class.
/// </summary>
protected ConvertFromToSecureStringCommandBase(string name) : base(name) { }
private SecureString _secureKey = null;
private byte[] _key;
/// <summary>
/// Gets or sets the SecureString version of the encryption
/// key used by the SecureString cmdlets.
/// </summary>
[Parameter(Position = 1, ParameterSetName = "Secure")]
public SecureString SecureKey
{
get
{
return _secureKey;
}
set
{
_secureKey = value;
}
}
/// <summary>
/// Gets or sets the byte version of the encryption
/// key used by the SecureString cmdlets.
/// </summary>
[Parameter(ParameterSetName = "Open")]
public byte[] Key
{
get
{
return _key;
}
set
{
_key = value;
}
}
}
/// <summary>
/// Defines the implementation of the 'ConvertFrom-SecureString' cmdlet.
/// This cmdlet exports a new SecureString -- one that represents
/// text that should be kept confidential. The text is encrypted
/// for privacy when being used, and deleted from computer memory
/// when no longer needed. When no key is specified, the command
/// uses the DPAPI to encrypt the string. When a key is specified, the
/// command uses the AES algorithm to encrypt the string.
/// </summary>
[Cmdlet(VerbsData.ConvertFrom, "SecureString", DefaultParameterSetName = "Secure", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096497")]
[OutputType(typeof(string))]
public sealed class ConvertFromSecureStringCommand : ConvertFromToSecureStringCommandBase
{
/// <summary>
/// Initializes a new instance of the ExportSecureStringCommand class.
/// </summary>
public ConvertFromSecureStringCommand() : base("ConvertFrom-SecureString") { }
/// <summary>
/// Gets or sets the secure string to be exported.
/// </summary>
[Parameter(Position = 0, ValueFromPipeline = true, Mandatory = true)]
public SecureString SecureString
{
get
{
return SecureStringData;
}
set
{
SecureStringData = value;
}
}
/// <summary>
/// Gets or sets a switch to get the secure string as plain text.
/// </summary>
[Parameter(ParameterSetName = "AsPlainText")]
public SwitchParameter AsPlainText { get; set; }
/// <summary>
/// Processes records from the input pipeline.
/// For each input object, the command encrypts
/// and exports the object.
/// </summary>
protected override void ProcessRecord()
{
string exportedString = null;
EncryptionResult encryptionResult = null;
const string argumentName = "SecureString";
Utils.CheckSecureStringArg(SecureStringData, argumentName);
if (SecureStringData.Length == 0)
{
throw PSTraceSource.NewArgumentException(argumentName);
}
if (SecureKey != null)
{
Dbg.Diagnostics.Assert(Key == null, "Only one encryption key should be specified");
encryptionResult = SecureStringHelper.Encrypt(SecureString, SecureKey);
}
else if (Key != null)
{
encryptionResult = SecureStringHelper.Encrypt(SecureString, Key);
}
else if (AsPlainText)
{
IntPtr valuePtr = IntPtr.Zero;
try
{
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(SecureString);
exportedString = Marshal.PtrToStringUni(valuePtr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
else
{
exportedString = SecureStringHelper.Protect(SecureString);
}
if (encryptionResult != null)
{
// The formatted string is Algorithm Version,
// Initialization Vector, Encrypted Data
string dataPackage = string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"{0}|{1}|{2}",
2,
encryptionResult.IV,
encryptionResult.EncryptedData);
// encode the package, and output it.
// We also include a recognizable prefix so that
// we can use the old decryption mechanism if we
// don't see it. While the old decryption
// generated invalid data for the first bit of the
// SecureString, it at least didn't generate an
// exception.
byte[] outputBytes = System.Text.Encoding.Unicode.GetBytes(dataPackage);
string encodedString = Convert.ToBase64String(outputBytes);
WriteObject(SecureStringHelper.SecureStringExportHeader + encodedString);
}
else if (exportedString != null)
{
WriteObject(exportedString);
}
}
}
/// <summary>
/// Defines the implementation of the 'ConvertTo-SecureString' cmdlet.
/// This cmdlet imports a new SecureString from encrypted data --
/// one that represents text that should be kept confidential.
/// The text is encrypted for privacy when being used, and deleted
/// from computer memory when no longer needed. When no key is
/// specified, the command uses the DPAPI to decrypt the data.
/// When a key is specified, the command uses the AES algorithm
/// to decrypt the data.
/// </summary>
[Cmdlet(VerbsData.ConvertTo, "SecureString", DefaultParameterSetName = "Secure", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096916")]
[OutputType(typeof(SecureString))]
public sealed class ConvertToSecureStringCommand : ConvertFromToSecureStringCommandBase
{
/// <summary>
/// Initializes a new instance of the ImportSecureStringCommand class.
/// </summary>
public ConvertToSecureStringCommand() : base("ConvertTo-SecureString") { }
/// <summary>
/// Gets or sets the unsecured string to be imported.
/// </summary>
[Parameter(Position = 0, ValueFromPipeline = true, Mandatory = true)]
public string String
{
get
{
return _s;
}
set
{
_s = value;
}
}
private string _s;
/// <summary>
/// Gets or sets the flag that marks the unsecured string as a plain
/// text string.
/// </summary>
[Parameter(Position = 1, ParameterSetName = "PlainText")]
public SwitchParameter AsPlainText
{
get
{
return _asPlainText;
}
set
{
_asPlainText = value;
}
}
private bool _asPlainText;
/// <summary>
/// Gets or sets the flag that will force the import of a plaintext
/// unsecured string.
/// </summary>
[Parameter(Position = 2, ParameterSetName = "PlainText")]
public SwitchParameter Force
{
get
{
return _force;
}
set
{
_force = value;
}
}
private bool _force;
/// <summary>
/// Processes records from the input pipeline.
/// For each input object, the command decrypts the data,
/// then exports a new SecureString created from the object.
/// </summary>
protected override void ProcessRecord()
{
SecureString importedString = null;
Utils.CheckArgForNullOrEmpty(_s, "String");
try
{
string encryptedContent = String;
byte[] iv = null;
// If this is a V2 package
if (String.IndexOf(SecureStringHelper.SecureStringExportHeader,
StringComparison.OrdinalIgnoreCase) == 0)
{
try
{
// Trim out the header, and retrieve the
// rest of the string
string remainingData = this.String.Substring(
SecureStringHelper.SecureStringExportHeader.Length,
String.Length - SecureStringHelper.SecureStringExportHeader.Length);
// Unpack it from Base64, get the string
// representation, then parse it into its components.
byte[] inputBytes = Convert.FromBase64String(remainingData);
string dataPackage = System.Text.Encoding.Unicode.GetString(inputBytes);
string[] dataElements = dataPackage.Split(Utils.Separators.Pipe);
if (dataElements.Length == 3)
{
encryptedContent = dataElements[2];
iv = Convert.FromBase64String(dataElements[1]);
}
}
catch (FormatException)
{
// Will be raised if we can't convert the
// input from a Base64 string. This means
// it's not really a V2 package.
encryptedContent = String;
iv = null;
}
}
if (SecureKey != null)
{
Dbg.Diagnostics.Assert(Key == null, "Only one encryption key should be specified");
importedString = SecureStringHelper.Decrypt(encryptedContent, SecureKey, iv);
}
else if (Key != null)
{
importedString = SecureStringHelper.Decrypt(encryptedContent, Key, iv);
}
else if (!AsPlainText)
{
importedString = SecureStringHelper.Unprotect(String);
}
else
{
importedString = SecureStringHelper.FromPlainTextString(String);
}
}
catch (ArgumentException e)
{
ErrorRecord er =
SecurityUtils.CreateInvalidArgumentErrorRecord(
e,
"ImportSecureString_InvalidArgument"
);
WriteError(er);
}
catch (CryptographicException e)
{
ErrorRecord er =
SecurityUtils.CreateInvalidArgumentErrorRecord(
e,
"ImportSecureString_InvalidArgument_CryptographicError"
);
WriteError(er);
}
if (importedString != null)
{
WriteObject(importedString);
}
}
}
}