2016-03-30 14:20:52 -07:00
/********************************************************************++
2017-10-27 05:12:19 +08:00
Copyright (c) Microsoft Corporation. All rights reserved.
2016-03-30 14:20:52 -07:00
--********************************************************************/
using System ;
using System.IO ;
using System.Security ;
using System.Security.Cryptography ;
using System.Runtime.InteropServices ;
using System.Globalization ;
using System.Management.Automation ;
using System.Management.Automation.Internal ;
using System.Text ;
namespace Microsoft.PowerShell
{
/// <summary>
/// helper class for secure string related functionality
/// </summary>
///
internal static class SecureStringHelper
{
// Some random hex characters to identify the beginning of a
// V2-exported SecureString.
internal static string SecureStringExportHeader = "76492d1116743f0423413b16050a5345" ;
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Create a new SecureString based on the specified binary data.
///
/// The binary data must be byte[] version of unicode char[],
/// otherwise the results are unpredictable.
/// </summary>
///
/// <param name="data"> input data </param>
///
/// <returns> a SecureString </returns>
///
private static SecureString New ( byte [] data )
{
2016-07-29 13:02:49 -07:00
if (( data . Length % 2 ) != 0 )
2016-03-30 14:20:52 -07:00
{
// If the data is not an even length, they supplied an invalid key
String error = Serialization . InvalidKey ;
throw new PSArgumentException ( error );
}
char ch ;
SecureString ss = new SecureString ();
//
2017-01-16 13:31:14 -08:00
// each unicode char is 2 bytes.
2016-03-30 14:20:52 -07:00
//
int len = data . Length / 2 ;
2016-07-29 13:02:49 -07:00
for ( int i = 0 ; i < len ; i ++)
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
ch = ( char )( data [ 2 * i + 1 ] * 256 + data [ 2 * i ]);
2016-03-30 14:20:52 -07:00
ss . AppendChar ( ch );
//
// zero out the data slots as soon as we use them
//
2016-07-29 13:02:49 -07:00
data [ 2 * i ] = 0 ;
data [ 2 * i + 1 ] = 0 ;
2016-03-30 14:20:52 -07:00
}
return ss ;
}
/// <summary>
/// get the contents of a SecureString as byte[]
/// </summary>
///
/// <param name="s"> input string </param>
///
/// <returns> contents of s (char[]) converted to byte[] </returns>
///
2016-04-28 13:41:25 -07:00
[ArchitectureSensitive]
2016-03-30 14:20:52 -07:00
internal static byte [] GetData ( SecureString s )
{
//
2017-01-16 13:31:14 -08:00
// each unicode char is 2 bytes.
2016-03-30 14:20:52 -07:00
//
2016-07-29 13:02:49 -07:00
byte [] data = new byte [ s . Length * 2 ];
2016-03-30 14:20:52 -07:00
if ( s . Length > 0 )
{
2017-06-05 20:59:30 -07:00
IntPtr ptr = Marshal . SecureStringToCoTaskMemUnicode ( s );
2016-03-30 14:20:52 -07:00
try
{
Marshal . Copy ( ptr , data , 0 , data . Length );
}
finally
{
2016-06-27 10:15:11 -07:00
Marshal . ZeroFreeCoTaskMemUnicode ( ptr );
2016-03-30 14:20:52 -07:00
}
}
return data ;
}
/// <summary>
/// Encode the specified byte[] as a unicode string.
///
/// Currently we use simple hex encoding but this
/// method can be changed to use a better encoding
/// such as base64.
/// </summary>
///
/// <param name="data"> binary data to encode </param>
///
/// <returns> a string representing encoded data </returns>
///
internal static string ByteArrayToString ( byte [] data )
{
StringBuilder sb = new StringBuilder ();
2016-07-29 13:02:49 -07:00
for ( int i = 0 ; i < data . Length ; i ++)
2016-03-30 14:20:52 -07:00
{
sb . Append ( data [ i ]. ToString ( "x2" , System . Globalization . CultureInfo . InvariantCulture ));
}
return sb . ToString ();
}
/// <summary>
/// Convert a string obtained using ByteArrayToString()
/// back to byte[] format.
/// </summary>
///
/// <param name="s"> encoded input string </param>
///
/// <returns> bin data as byte[] </returns>
///
internal static byte [] ByteArrayFromString ( string s )
{
//
// two hex chars per byte
//
2016-07-29 13:02:49 -07:00
int dataLen = s . Length / 2 ;
2016-03-30 14:20:52 -07:00
byte [] data = new byte [ dataLen ];
if ( s . Length > 0 )
{
2016-07-29 13:02:49 -07:00
for ( int i = 0 ; i < dataLen ; i ++)
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
data [ i ] = byte . Parse ( s . Substring ( 2 * i , 2 ),
NumberStyles . AllowHexSpecifier ,
2016-03-30 14:20:52 -07:00
System . Globalization . CultureInfo . InvariantCulture );
}
}
return data ;
}
/// <summary>
/// return contents of the SecureString after encrypting
/// using DPAPI and encoding the encrypted blob as a string
/// </summary>
///
/// <param name="input"> SecureString to protect </param>
///
/// <returns> a string (see summary) </returns>
///
internal static string Protect ( SecureString input )
{
Utils . CheckSecureStringArg ( input , "input" );
string output = "" ;
byte [] data = null ;
byte [] protectedData = null ;
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
data = GetData ( input );
protectedData = ProtectedData . Protect ( data , null ,
DataProtectionScope . CurrentUser );
2016-07-29 13:02:49 -07:00
for ( int i = 0 ; i < data . Length ; i ++)
2016-03-30 14:20:52 -07:00
{
data [ i ] = 0 ;
}
output = ByteArrayToString ( protectedData );
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
return output ;
}
/// <summary>
/// Decrypts the specified string using DPAPI and return
/// equivalent SecureString.
///
/// The string must be obtained earlier by a call to Protect()
/// </summary>
///
/// <param name="input"> encrypted string </param>
///
/// <returns> SecureString </returns>
///
internal static SecureString Unprotect ( string input )
{
Utils . CheckArgForNullOrEmpty ( input , "input" );
if (( input . Length % 2 ) != 0 )
{
throw PSTraceSource . NewArgumentException ( "input" , Serialization . InvalidEncryptedString , input );
}
byte [] data = null ;
byte [] protectedData = null ;
SecureString s ;
protectedData = ByteArrayFromString ( input );
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
data = ProtectedData . Unprotect ( protectedData , null ,
DataProtectionScope . CurrentUser );
s = New ( data );
return s ;
}
/// <summary>
/// return contents of the SecureString after encrypting
/// using the specified key and encoding the encrypted blob as a string
/// </summary>
///
/// <param name="input"> input string to encrypt </param>
///
/// <param name="key"> encryption key </param>
///
/// <returns> a string (see summary) </returns>
///
/// <remarks> </remarks>
///
internal static EncryptionResult Encrypt ( SecureString input , SecureString key )
{
EncryptionResult output = null ;
//
// get clear text key from the SecureString key
//
byte [] keyBlob = GetData ( key );
//
// encrypt the data
//
output = Encrypt ( input , keyBlob );
//
// clear the clear text key
//
Array . Clear ( keyBlob , 0 , keyBlob . Length );
return output ;
}
/// <summary>
/// return contents of the SecureString after encrypting
/// using the specified key and encoding the encrypted blob as a string
/// </summary>
///
/// <param name="input"> input string to encrypt </param>
///
/// <param name="key"> encryption key </param>
///
/// <returns> a string (see summary) </returns>
///
/// <remarks> </remarks>
///
internal static EncryptionResult Encrypt ( SecureString input , byte [] key )
{
return Encrypt ( input , key , null );
}
internal static EncryptionResult Encrypt ( SecureString input , byte [] key , byte [] iv )
{
Utils . CheckSecureStringArg ( input , "input" );
Utils . CheckKeyArg ( key , "key" );
byte [] encryptedData = null ;
MemoryStream ms = null ;
ICryptoTransform encryptor = null ;
CryptoStream cs = null ;
//
// prepare the crypto stuff. Initialization Vector is
// randomized by default.
//
Aes aes = Aes . Create ();
if ( iv == null )
iv = aes . IV ;
encryptor = aes . CreateEncryptor ( key , iv );
ms = new MemoryStream ();
using ( cs = new CryptoStream ( ms , encryptor , CryptoStreamMode . Write ))
{
//
// get clear text data from the input SecureString
//
byte [] data = GetData ( input );
//
// encrypt it
//
cs . Write ( data , 0 , data . Length );
cs . FlushFinalBlock ();
//
// clear the clear text data array
//
Array . Clear ( data , 0 , data . Length );
//
// convert the encrypted blob to a string
//
encryptedData = ms . ToArray ();
EncryptionResult output = new EncryptionResult ( ByteArrayToString ( encryptedData ), Convert . ToBase64String ( iv ));
return output ;
}
}
/// <summary>
/// Decrypts the specified string using the specified key
/// and return equivalent SecureString.
///
/// The string must be obtained earlier by a call to Encrypt()
/// </summary>
///
/// <param name="input"> encrypted string </param>
///
/// <param name="key"> encryption key </param>
///
/// <param name="IV"> encryption initialization vector. If this is set to null, the method uses internally computed strong random number as IV </param>
///
/// <returns> SecureString </returns>
///
internal static SecureString Decrypt ( string input , SecureString key , byte [] IV )
{
SecureString output = null ;
//
// get clear text key from the SecureString key
//
byte [] keyBlob = GetData ( key );
//
// decrypt the data
//
output = Decrypt ( input , keyBlob , IV );
//
// clear the clear text key
//
Array . Clear ( keyBlob , 0 , keyBlob . Length );
2016-07-29 13:02:49 -07:00
return output ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Decrypts the specified string using the specified key
/// and return equivalent SecureString.
///
/// The string must be obtained earlier by a call to Encrypt()
/// </summary>
///
/// <param name="input"> encrypted string </param>
///
/// <param name="key"> encryption key </param>
///
/// <param name="IV"> encryption initialization vector. If this is set to null, the method uses internally computed strong random number as IV </param>
///
/// <returns> SecureString </returns>
///
internal static SecureString Decrypt ( string input , byte [] key , byte [] IV )
{
Utils . CheckArgForNullOrEmpty ( input , "input" );
Utils . CheckKeyArg ( key , "key" );
byte [] decryptedData = null ;
byte [] encryptedData = null ;
SecureString s = null ;
//
// prepare the crypto stuff
//
Aes aes = Aes . Create ();
encryptedData = ByteArrayFromString ( input );
2016-08-02 22:25:13 -07:00
var decryptor = aes . CreateDecryptor ( key , IV ?? aes . IV );
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
MemoryStream ms = new MemoryStream ( encryptedData );
using ( CryptoStream cs = new CryptoStream ( ms , decryptor , CryptoStreamMode . Read ))
{
byte [] tempDecryptedData = new byte [ encryptedData . Length ];
int numBytesRead = 0 ;
//
// decrypt the data
//
numBytesRead = cs . Read ( tempDecryptedData , 0 ,
tempDecryptedData . Length );
decryptedData = new byte [ numBytesRead ];
for ( int i = 0 ; i < numBytesRead ; i ++)
{
decryptedData [ i ] = tempDecryptedData [ i ];
}
s = New ( decryptedData );
Array . Clear ( decryptedData , 0 , decryptedData . Length );
Array . Clear ( tempDecryptedData , 0 , tempDecryptedData . Length );
return s ;
}
}
}
/// <summary>
/// Helper class to return encryption results, and the IV used to
/// do the encryption
/// </summary>
///
internal class EncryptionResult
{
internal EncryptionResult ( string encrypted , string IV )
{
2016-08-02 13:29:09 -07:00
EncryptedData = encrypted ;
this . IV = IV ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Gets the encrypted data
/// </summary>
2016-08-02 13:29:09 -07:00
internal String EncryptedData { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Gets the IV used to encrypt the data
/// </summary>
2016-08-02 13:29:09 -07:00
internal String IV { get ; }
2016-03-30 14:20:52 -07:00
}
#if CORECLR
2017-01-16 13:31:14 -08:00
// The DPAPIs implemented in this section are temporary workaround.
2016-03-30 14:20:52 -07:00
// CoreCLR team will bring 'ProtectedData' type to Project K eventually.
#region DPAPI
internal enum DataProtectionScope
{
CurrentUser = 0x00 ,
LocalMachine = 0x01
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
internal static class ProtectedData
{
/// <summary>
/// Protect
/// </summary>
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 ();
CAPI . CRYPTOAPI_BLOB blob = new CAPI . CRYPTOAPI_BLOB ();
try
{
pbDataIn = GCHandle . Alloc ( userData , GCHandleType . Pinned );
CAPI . CRYPTOAPI_BLOB dataIn = new CAPI . CRYPTOAPI_BLOB ();
dataIn . cbData = ( uint ) userData . Length ;
dataIn . pbData = pbDataIn . AddrOfPinnedObject ();
CAPI . CRYPTOAPI_BLOB entropy = new CAPI . CRYPTOAPI_BLOB ();
if ( optionalEntropy != null )
{
pOptionalEntropy = GCHandle . Alloc ( optionalEntropy , GCHandleType . Pinned );
entropy . cbData = ( uint ) optionalEntropy . Length ;
entropy . pbData = pOptionalEntropy . AddrOfPinnedObject ();
}
uint dwFlags = CAPI . CRYPTPROTECT_UI_FORBIDDEN ;
if ( scope == DataProtectionScope . LocalMachine )
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 )))
{
int lastWin32Error = Marshal . GetLastWin32Error ();
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
// One of the most common reasons that DPAPI operations fail is that the user
// profile is not loaded (for instance in the case of impersonation or running in a
// service. In those cases, throw an exception that provides more specific details
// about what happened.
if ( CAPI . ErrorMayBeCausedByUnloadedProfile ( lastWin32Error ))
{
throw new CryptographicException ( "Cryptography_DpApi_ProfileMayNotBeLoaded" );
}
else
{
throw new CryptographicException ( lastWin32Error );
}
}
}
// 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 );
return encryptedData ;
}
finally
{
if ( pbDataIn . IsAllocated )
pbDataIn . Free ();
if ( pOptionalEntropy . IsAllocated )
pOptionalEntropy . Free ();
if ( blob . pbData != IntPtr . Zero )
{
CAPI . ZeroMemory ( blob . pbData , blob . cbData );
CAPI . LocalFree ( blob . pbData );
}
}
}
/// <summary>
/// Unprotect
/// </summary>
public static byte [] Unprotect ( byte [] encryptedData , byte [] optionalEntropy , DataProtectionScope scope )
{
if ( encryptedData == null )
throw new ArgumentNullException ( "encryptedData" );
GCHandle pbDataIn = new GCHandle ();
GCHandle pOptionalEntropy = new GCHandle ();
CAPI . CRYPTOAPI_BLOB userData = new CAPI . CRYPTOAPI_BLOB ();
try
{
pbDataIn = GCHandle . Alloc ( encryptedData , GCHandleType . Pinned );
CAPI . CRYPTOAPI_BLOB dataIn = new CAPI . CRYPTOAPI_BLOB ();
dataIn . cbData = ( uint ) encryptedData . Length ;
dataIn . pbData = pbDataIn . AddrOfPinnedObject ();
CAPI . CRYPTOAPI_BLOB entropy = new CAPI . CRYPTOAPI_BLOB ();
if ( optionalEntropy != null )
{
pOptionalEntropy = GCHandle . Alloc ( optionalEntropy , GCHandleType . Pinned );
entropy . cbData = ( uint ) optionalEntropy . Length ;
entropy . pbData = pOptionalEntropy . AddrOfPinnedObject ();
}
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 )))
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 );
return data ;
}
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 );
}
}
}
}
internal static class CAPI
{
internal const uint CRYPTPROTECT_UI_FORBIDDEN = 0x1 ;
internal const uint CRYPTPROTECT_LOCAL_MACHINE = 0x4 ;
internal const int E_FILENOTFOUND = unchecked (( int ) 0x80070002 ); // File not found
internal const int ERROR_FILE_NOT_FOUND = 2 ; // File not found
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct CRYPTOAPI_BLOB
{
internal uint cbData ;
internal IntPtr pbData ;
}
internal static bool ErrorMayBeCausedByUnloadedProfile ( int errorCode )
{
// CAPI returns a file not found error if the user profile is not yet loaded
return errorCode == E_FILENOTFOUND ||
errorCode == ERROR_FILE_NOT_FOUND ;
}
[DllImport("CRYPT32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CryptProtectData (
[In] IntPtr pDataIn ,
[In] string szDataDescr ,
[In] IntPtr pOptionalEntropy ,
[In] IntPtr pvReserved ,
[In] IntPtr pPromptStruct ,
[In] uint dwFlags ,
[In, Out] IntPtr pDataBlob );
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
[DllImport("CRYPT32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CryptUnprotectData (
[In] IntPtr pDataIn ,
[In] IntPtr ppszDataDescr ,
[In] IntPtr pOptionalEntropy ,
[In] IntPtr pvReserved ,
[In] IntPtr pPromptStruct ,
[In] uint dwFlags ,
[In, Out] IntPtr pDataBlob );
[DllImport("ntdll.dll", EntryPoint = "RtlZeroMemory", SetLastError = true)]
internal static extern void ZeroMemory ( IntPtr handle , uint length );
[DllImport(PinvokeDllNames.LocalFreeDllName, SetLastError = true)]
internal static extern IntPtr LocalFree ( IntPtr handle );
}
#endregion DPAPI
#endif
}