mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Enable SecureString cmdlets for non-Windows (#9199)
On Unix systems, fallback to plaintext manipulation instead of using the DPAPI which is not available. ## PR Context Currently, existing scripts that use SecureString cmdlets fail with an error complaining about crypt32.dll not being available. This change allows these cmdlets to be used, but there is no encryption of the string. .Net already [states](https://docs.microsoft.com/en-us/dotnet/api/system.security.securestring?view=netcore-2.1#remarks) that the contents of a SecureString are not encrypted on .Net Core. Fix https://github.com/PowerShell/PowerShell/issues/1654 Co-authored-by: Travis Plunk <travis.plunk@microsoft.com>
This commit is contained in:
co-authored by
Travis Plunk
parent
d4eed9ea2a
commit
9e608ac55f
@@ -155,12 +155,17 @@ namespace Microsoft.PowerShell
|
||||
byte[] protectedData = null;
|
||||
|
||||
data = GetData(input);
|
||||
#if UNIX
|
||||
// DPAPI doesn't exist on UNIX so we simply use the string as a byte-array
|
||||
protectedData = data;
|
||||
#else
|
||||
protectedData = ProtectedData.Protect(data, null,
|
||||
DataProtectionScope.CurrentUser);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
output = ByteArrayToString(protectedData);
|
||||
|
||||
@@ -189,9 +194,14 @@ namespace Microsoft.PowerShell
|
||||
|
||||
protectedData = ByteArrayFromString(input);
|
||||
|
||||
#if UNIX
|
||||
// DPAPI isn't supported in UNIX, so we just translate the byte-array back to a string
|
||||
data = protectedData;
|
||||
#else
|
||||
data = ProtectedData.Unprotect(protectedData, null,
|
||||
DataProtectionScope.CurrentUser);
|
||||
|
||||
#endif
|
||||
s = New(data);
|
||||
|
||||
return s;
|
||||
@@ -400,7 +410,7 @@ namespace Microsoft.PowerShell
|
||||
internal string IV { get; }
|
||||
}
|
||||
|
||||
#if CORECLR
|
||||
#if !UNIX
|
||||
|
||||
// The DPAPIs implemented in this section are temporary workaround.
|
||||
// CoreCLR team will bring 'ProtectedData' type to Project K eventually.
|
||||
@@ -421,7 +431,9 @@ namespace Microsoft.PowerShell
|
||||
public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtectionScope scope)
|
||||
{
|
||||
if (userData == null)
|
||||
{
|
||||
throw new ArgumentNullException("userData");
|
||||
}
|
||||
|
||||
GCHandle pbDataIn = new GCHandle();
|
||||
GCHandle pOptionalEntropy = new GCHandle();
|
||||
@@ -446,13 +458,14 @@ namespace Microsoft.PowerShell
|
||||
dwFlags |= CAPI.CRYPTPROTECT_LOCAL_MACHINE;
|
||||
unsafe
|
||||
{
|
||||
if (!CAPI.CryptProtectData(new IntPtr(&dataIn),
|
||||
string.Empty,
|
||||
new IntPtr(&entropy),
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
dwFlags,
|
||||
new IntPtr(&blob)))
|
||||
if (!CAPI.CryptProtectData(
|
||||
pDataIn: new IntPtr(&dataIn),
|
||||
szDataDescr: string.Empty,
|
||||
pOptionalEntropy: new IntPtr(&entropy),
|
||||
pvReserved: IntPtr.Zero,
|
||||
pPromptStruct: IntPtr.Zero,
|
||||
dwFlags: dwFlags,
|
||||
pDataBlob: new IntPtr(&blob)))
|
||||
{
|
||||
int lastWin32Error = Marshal.GetLastWin32Error();
|
||||
|
||||
@@ -473,7 +486,9 @@ namespace Microsoft.PowerShell
|
||||
|
||||
// In some cases, the API would fail due to OOM but simply return a null pointer.
|
||||
if (blob.pbData == IntPtr.Zero)
|
||||
{
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
byte[] encryptedData = new byte[(int)blob.cbData];
|
||||
Marshal.Copy(blob.pbData, encryptedData, 0, encryptedData.Length);
|
||||
@@ -483,9 +498,13 @@ namespace Microsoft.PowerShell
|
||||
finally
|
||||
{
|
||||
if (pbDataIn.IsAllocated)
|
||||
{
|
||||
pbDataIn.Free();
|
||||
}
|
||||
if (pOptionalEntropy.IsAllocated)
|
||||
{
|
||||
pOptionalEntropy.Free();
|
||||
}
|
||||
if (blob.pbData != IntPtr.Zero)
|
||||
{
|
||||
CAPI.ZeroMemory(blob.pbData, blob.cbData);
|
||||
@@ -499,11 +518,10 @@ namespace Microsoft.PowerShell
|
||||
/// </summary>
|
||||
public static byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope)
|
||||
{
|
||||
#if UNIX
|
||||
throw new PlatformNotSupportedException(Serialization.DeserializeSecureStringNotSupported);
|
||||
#else
|
||||
if (encryptedData == null)
|
||||
{
|
||||
throw new ArgumentNullException("encryptedData");
|
||||
}
|
||||
|
||||
GCHandle pbDataIn = new GCHandle();
|
||||
GCHandle pOptionalEntropy = new GCHandle();
|
||||
@@ -525,22 +543,30 @@ namespace Microsoft.PowerShell
|
||||
|
||||
uint dwFlags = CAPI.CRYPTPROTECT_UI_FORBIDDEN;
|
||||
if (scope == DataProtectionScope.LocalMachine)
|
||||
{
|
||||
dwFlags |= CAPI.CRYPTPROTECT_LOCAL_MACHINE;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
if (!CAPI.CryptUnprotectData(new IntPtr(&dataIn),
|
||||
IntPtr.Zero,
|
||||
new IntPtr(&entropy),
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
dwFlags,
|
||||
new IntPtr(&userData)))
|
||||
if (!CAPI.CryptUnprotectData(
|
||||
pDataIn: new IntPtr(&dataIn),
|
||||
ppszDataDescr: IntPtr.Zero,
|
||||
pOptionalEntropy: new IntPtr(&entropy),
|
||||
pvReserved: IntPtr.Zero,
|
||||
pPromptStruct: IntPtr.Zero,
|
||||
dwFlags: dwFlags,
|
||||
pDataBlob: new IntPtr(&userData)))
|
||||
{
|
||||
throw new CryptographicException(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
|
||||
// In some cases, the API would fail due to OOM but simply return a null pointer.
|
||||
if (userData.pbData == IntPtr.Zero)
|
||||
{
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
byte[] data = new byte[(int)userData.cbData];
|
||||
Marshal.Copy(userData.pbData, data, 0, data.Length);
|
||||
@@ -550,16 +576,19 @@ namespace Microsoft.PowerShell
|
||||
finally
|
||||
{
|
||||
if (pbDataIn.IsAllocated)
|
||||
{
|
||||
pbDataIn.Free();
|
||||
}
|
||||
if (pOptionalEntropy.IsAllocated)
|
||||
{
|
||||
pOptionalEntropy.Free();
|
||||
}
|
||||
if (userData.pbData != IntPtr.Zero)
|
||||
{
|
||||
CAPI.ZeroMemory(userData.pbData, userData.cbData);
|
||||
CAPI.LocalFree(userData.pbData);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,6 @@ Describe "SecureString conversion tests" -Tags "CI" {
|
||||
$string = "ABCD"
|
||||
$secureString = [System.Security.SecureString]::New()
|
||||
$string.ToCharArray() | foreach-object { $securestring.AppendChar($_) }
|
||||
$defaultParamValues = $PSdefaultParameterValues.Clone()
|
||||
$PSdefaultParameterValues = @{}
|
||||
if ( ! $IsWindows ) { $PSdefaultParameterValues["it:pending"] = $true }
|
||||
}
|
||||
AfterAll {
|
||||
$global:PSdefaultParameterValues = $defaultParamValues
|
||||
}
|
||||
|
||||
It "using null arguments to ConvertFrom-SecureString produces an exception" {
|
||||
|
||||
@@ -170,29 +170,7 @@ Describe "CliXml test" -Tags "CI" {
|
||||
$testPath | Should -Not -Exist
|
||||
}
|
||||
|
||||
It "should fail to import PSCredential on non-Windows" -Skip:$IsWindows {
|
||||
$cliXml = @"
|
||||
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
|
||||
<Obj RefId="0">
|
||||
<TN RefId="0">
|
||||
<T>System.Management.Automation.PSCredential</T>
|
||||
<T>System.Object</T>
|
||||
</TN>
|
||||
<ToString>System.Management.Automation.PSCredential</ToString>
|
||||
<Props>
|
||||
<S N="UserName">foo</S>
|
||||
<!-- this is just an empty password, but needs to be a valid hash -->
|
||||
<SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb01000000977756228672474da9bfbe7b6acc02cb0000000002000000000003660000c0000000100000002529194617877bbbc952c6aacb6535f00000000004800000a000000010000000703ca974777a8335d5e2f8b3e592ff4208000000635d9fcda035734e140000001b6940e3222117a1014c67377e8786549f3dca29</SS>
|
||||
</Props>
|
||||
</Obj>
|
||||
</Objs>
|
||||
"@
|
||||
$path = "$testdrive/cred.xml"
|
||||
Set-Content -Path $path -Value $cliXml
|
||||
{ Import-Clixml -Path $path } | Should -Throw -ErrorId "System.PlatformNotSupportedException,Microsoft.PowerShell.Commands.ImportClixmlCommand"
|
||||
}
|
||||
|
||||
It "should import PSCredential" -Skip:(!$IsWindows) {
|
||||
It "should import PSCredential" {
|
||||
$UserName = "Foo"
|
||||
$pass = ConvertTo-SecureString (New-RandomHexString) -AsPlainText -Force
|
||||
$cred = [PSCredential]::new($UserName, $pass)
|
||||
|
||||
Reference in New Issue
Block a user