/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System.Text; using System.Security; using System.Threading; using System.Runtime.InteropServices; using System.Runtime.Serialization; using Microsoft.Win32.SafeHandles; using System.Diagnostics.CodeAnalysis; using Dbg = System.Management.Automation.Diagnostics; using System.Management.Automation.Remoting; #if CORECLR // Use stubs for SerializableAttribute, SecurityPermissionAttribute, ReliabilityContractAttribute and ISerializable related types. using Microsoft.PowerShell.CoreClr.Stubs; #else using System.Runtime.ConstrainedExecution; #endif namespace System.Management.Automation.Internal { /// /// Class that encapsulates native crypto provider handles and provides a /// mechanism for resources released by them /// // [SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)] // [SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode=true)] internal class PSSafeCryptProvHandle : SafeHandleZeroOrMinusOneIsInvalid { /// /// This safehandle instance "owns" the handle, hence base(true) /// is being called. When safehandle is no longer in use it will /// call this class's ReleaseHandle method which will release /// the resources /// internal PSSafeCryptProvHandle() : base(true) { } /// /// Release the crypto handle held by this instance /// /// true on success, false otherwise [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return PSCryptoNativeUtils.CryptReleaseContext(handle, 0); } } /// /// Class the encapsulates native crypto key handles and provides a /// mechanism to release resources used by it /// //[SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)] //[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode=true)] internal class PSSafeCryptKey : SafeHandleZeroOrMinusOneIsInvalid { /// /// This safehandle instance "owns" the handle, hence base(true) /// is being called. When safehandle is no longer in use it will /// call this class's ReleaseHandle method which will release the /// resources /// internal PSSafeCryptKey() : base(true) { } /// /// Release the crypto handle held by this instance /// /// true on success, false otherwise [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return PSCryptoNativeUtils.CryptDestroyKey(handle); } /// /// Equivalent of IntPtr.Zero for the safe crypt key /// internal static PSSafeCryptKey Zero { get; } = new PSSafeCryptKey(); } /// /// This class provides the wrapper for all Native CAPI functions /// internal class PSCryptoNativeUtils { #region Functions /// Return Type: BOOL->int ///hProv: HCRYPTPROV->ULONG_PTR->unsigned int ///Algid: ALG_ID->unsigned int ///dwFlags: DWORD->unsigned int ///phKey: HCRYPTKEY* [DllImportAttribute(PinvokeDllNames.CryptGenKeyDllName, EntryPoint = "CryptGenKey")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptGenKey(PSSafeCryptProvHandle hProv, uint Algid, uint dwFlags, ref PSSafeCryptKey phKey); /// Return Type: BOOL->int ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int [DllImportAttribute(PinvokeDllNames.CryptDestroyKeyDllName, EntryPoint = "CryptDestroyKey")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptDestroyKey(IntPtr hKey); /// Return Type: BOOL->int ///phProv: HCRYPTPROV* ///szContainer: LPCWSTR->WCHAR* ///szProvider: LPCWSTR->WCHAR* ///dwProvType: DWORD->unsigned int ///dwFlags: DWORD->unsigned int [DllImportAttribute(PinvokeDllNames.CryptAcquireContextDllName, EntryPoint = "CryptAcquireContext")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptAcquireContext(ref PSSafeCryptProvHandle phProv, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string szContainer, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string szProvider, uint dwProvType, uint dwFlags); /// Return Type: BOOL->int ///hProv: HCRYPTPROV->ULONG_PTR->unsigned int ///dwFlags: DWORD->unsigned int [DllImportAttribute(PinvokeDllNames.CryptReleaseContextDllName, EntryPoint = "CryptReleaseContext")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags); /// Return Type: BOOL->int ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int ///hHash: HCRYPTHASH->ULONG_PTR->unsigned int ///Final: BOOL->int ///dwFlags: DWORD->unsigned int ///pbData: BYTE* ///pdwDataLen: DWORD* ///dwBufLen: DWORD->unsigned int [DllImportAttribute(PinvokeDllNames.CryptEncryptDllName, EntryPoint = "CryptEncrypt")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptEncrypt(PSSafeCryptKey hKey, IntPtr hHash, [MarshalAsAttribute(UnmanagedType.Bool)] bool Final, uint dwFlags, byte[] pbData, ref int pdwDataLen, int dwBufLen); /// Return Type: BOOL->int ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int ///hHash: HCRYPTHASH->ULONG_PTR->unsigned int ///Final: BOOL->int ///dwFlags: DWORD->unsigned int ///pbData: BYTE* ///pdwDataLen: DWORD* [DllImportAttribute(PinvokeDllNames.CryptDecryptDllName, EntryPoint = "CryptDecrypt")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptDecrypt(PSSafeCryptKey hKey, IntPtr hHash, [MarshalAsAttribute(UnmanagedType.Bool)] bool Final, uint dwFlags, byte[] pbData, ref int pdwDataLen); /// Return Type: BOOL->int ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int ///hExpKey: HCRYPTKEY->ULONG_PTR->unsigned int ///dwBlobType: DWORD->unsigned int ///dwFlags: DWORD->unsigned int ///pbData: BYTE* ///pdwDataLen: DWORD* [DllImportAttribute(PinvokeDllNames.CryptExportKeyDllName, EntryPoint = "CryptExportKey")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptExportKey(PSSafeCryptKey hKey, PSSafeCryptKey hExpKey, uint dwBlobType, uint dwFlags, byte[] pbData, ref uint pdwDataLen); /// Return Type: BOOL->int ///hProv: HCRYPTPROV->ULONG_PTR->unsigned int ///pbData: BYTE* ///dwDataLen: DWORD->unsigned int ///hPubKey: HCRYPTKEY->ULONG_PTR->unsigned int ///dwFlags: DWORD->unsigned int ///phKey: HCRYPTKEY* [DllImportAttribute(PinvokeDllNames.CryptImportKeyDllName, EntryPoint = "CryptImportKey")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptImportKey(PSSafeCryptProvHandle hProv, byte[] pbData, int dwDataLen, PSSafeCryptKey hPubKey, uint dwFlags, ref PSSafeCryptKey phKey); /// Return Type: BOOL->int ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int ///pdwReserved: DWORD* ///dwFlags: DWORD->unsigned int ///phKey: HCRYPTKEY* [DllImportAttribute(PinvokeDllNames.CryptDuplicateKeyDllName, EntryPoint = "CryptDuplicateKey")] [return: MarshalAsAttribute(UnmanagedType.Bool)] public static extern bool CryptDuplicateKey(PSSafeCryptKey hKey, ref uint pdwReserved, uint dwFlags, ref PSSafeCryptKey phKey); /// Return Type: DWORD->unsigned int [DllImportAttribute(PinvokeDllNames.GetLastErrorDllName, EntryPoint = "GetLastError")] public static extern uint GetLastError(); #endregion Functions #region Constants /// /// Do not use persisted private key /// public const uint CRYPT_VERIFYCONTEXT = 0xF0000000; /// /// Mark the key for export /// public const uint CRYPT_EXPORTABLE = 0x00000001; /// /// Automatically assign a salt value when creating a /// session key /// public const int CRYPT_CREATE_SALT = 4; /// /// RSA Provider /// public const int PROV_RSA_FULL = 1; /// /// RSA Provider that supports AES /// encryption /// public const int PROV_RSA_AES = 24; /// /// Public key to be used for encryption /// public const int AT_KEYEXCHANGE = 1; /// /// RSA Key /// public const int CALG_RSA_KEYX = (PSCryptoNativeUtils.ALG_CLASS_KEY_EXCHANGE | (PSCryptoNativeUtils.ALG_TYPE_RSA | PSCryptoNativeUtils.ALG_SID_RSA_ANY)); /// /// Create a key for encryption /// public const int ALG_CLASS_KEY_EXCHANGE = (5) << (13); /// /// Create a RSA key pair /// public const int ALG_TYPE_RSA = (2) << (9); /// /// /// public const int ALG_SID_RSA_ANY = 0; /// /// Option for exporting public key blob /// public const int PUBLICKEYBLOB = 6; /// /// Option for exporting a session key /// public const int SIMPLEBLOB = 1; /// /// AES 256 symmetric key /// public const int CALG_AES_256 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256); /// /// ALG_CLASS_DATA_ENCRYPT /// public const int ALG_CLASS_DATA_ENCRYPT = (3) << (13); /// /// ALG_TYPE_BLOCK /// public const int ALG_TYPE_BLOCK = (3) << (9); /// /// ALG_SID_AES_256 -> 16 /// public const int ALG_SID_AES_256 = 16; /// CALG_AES_128 -> (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_128) public const int CALG_AES_128 = (ALG_CLASS_DATA_ENCRYPT | (ALG_TYPE_BLOCK | ALG_SID_AES_128)); /// ALG_SID_AES_128 -> 14 public const int ALG_SID_AES_128 = 14; #endregion Constants } /// /// Defines a custom exception which is thrown when /// a native CAPI call results in an error /// /// This exception is currently internal as it's not /// surfaced to the user. However, if we decide to surface errors /// to the user when something fails on the remote end, then this /// can be turned public [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")] [Serializable] internal class PSCryptoException : Exception { #region Private Members private uint _errorCode; #endregion Private Members #region Internal Properties /// /// Error code returned by the native CAPI call /// internal uint ErrorCode { get { return _errorCode; } } #endregion Internal Properties #region Constructors /// /// Default constructor /// public PSCryptoException() : this(0, new StringBuilder(String.Empty)) { } /// /// Constructor that will be used from within CryptoUtils /// /// error code returned by native /// crypto application /// error message associated with this failure public PSCryptoException(uint errorCode, StringBuilder message) : base(message.ToString()) { _errorCode = errorCode; } /// /// Constructor with just message but no inner exception /// /// error message associated with this failure public PSCryptoException(String message) : this(message, null) { } /// /// Constructor with inner exception /// /// error message /// innter exception /// This constructor is currently not called /// explicitly from crypto utils public PSCryptoException(string message, Exception innerException) : base(message, innerException) { _errorCode = unchecked((uint)-1); } #if !CORECLR /// /// Constructor which has type specific serialization logic /// /// serialization info /// context in which this constructor is called /// Currently no custom type-specific serialization logic is /// implemented protected PSCryptoException(SerializationInfo info, StreamingContext context) : base(info, context) { _errorCode = unchecked(0xFFFFFFF); Dbg.Assert(false, "type-specific serialization logic not implemented and so this constructor should not be called"); } #endif #endregion Constructors #region ISerializable Overrides #if !CORECLR /// /// Returns base implementation /// /// serialization info /// context public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); } #endif #endregion ISerializable Overrides } /// /// One of the issues with RSACryptoServiceProvider is that it never uses CRYPT_VERIFYCONTEXT /// to create ephemeral keys. This class is a facade written on top of native CAPI APIs /// to create ephemeral keys. /// internal class PSRSACryptoServiceProvider : IDisposable { #region Private Members private PSSafeCryptProvHandle _hProv; // handle to the provider private bool _canEncrypt = false; // this flag indicates that this class has a key // imported from the remote end and so can be // used for encryption private PSSafeCryptKey _hRSAKey; // handle to the RSA key with which the session // key is exchange. This can either be generated // or imported private PSSafeCryptKey _hSessionKey; // handle to the session key. This can either // be generated or imported private bool _sessionKeyGenerated = false; // bool indicating if session key was generated before private static PSSafeCryptProvHandle s_hStaticProv; private static PSSafeCryptKey s_hStaticRSAKey; private static bool s_keyPairGenerated = false; private static object s_syncObject = new object(); #endregion Private Members #region Constructors /// /// Private constructor /// /// indicates if this service /// provider is operating in server mode private PSRSACryptoServiceProvider(bool serverMode) { if (serverMode) { _hProv = new PSSafeCryptProvHandle(); // We need PROV_RSA_AES to support AES-256 symmetric key // encryption. PROV_RSA_FULL supports only RC2 and RC4 bool ret = PSCryptoNativeUtils.CryptAcquireContext(ref _hProv, null, null, PSCryptoNativeUtils.PROV_RSA_AES, PSCryptoNativeUtils.CRYPT_VERIFYCONTEXT); CheckStatus(ret); _hRSAKey = new PSSafeCryptKey(); } _hSessionKey = new PSSafeCryptKey(); } #endregion Constructors #region Internal Methods /// /// Get the public key as a base64 encoded string /// /// public key as base64 encoded string internal string GetPublicKeyAsBase64EncodedString() { uint publicKeyLength = 0; // Get key length first bool ret = PSCryptoNativeUtils.CryptExportKey(_hRSAKey, PSSafeCryptKey.Zero, PSCryptoNativeUtils.PUBLICKEYBLOB, 0, null, ref publicKeyLength); CheckStatus(ret); // Create enough buffer and get the actual data byte[] publicKey = new byte[publicKeyLength]; ret = PSCryptoNativeUtils.CryptExportKey(_hRSAKey, PSSafeCryptKey.Zero, PSCryptoNativeUtils.PUBLICKEYBLOB, 0, publicKey, ref publicKeyLength); CheckStatus(ret); // Convert the public key into base64 encoding so that it can be exported to // the other end. string result = Convert.ToBase64String(publicKey); return result; } /// /// Generates an AEX-256 sessin key if one is not already generated /// internal void GenerateSessionKey() { if (_sessionKeyGenerated) return; lock (s_syncObject) { if (!_sessionKeyGenerated) { bool ret = PSCryptoNativeUtils.CryptGenKey(_hProv, PSCryptoNativeUtils.CALG_AES_256, 0x01000000 | // key length = 256 bits PSCryptoNativeUtils.CRYPT_EXPORTABLE | PSCryptoNativeUtils.CRYPT_CREATE_SALT, ref _hSessionKey); CheckStatus(ret); _sessionKeyGenerated = true; _canEncrypt = true; // we can encrypt and decrypt once session key is available } } } /// /// 1. Generate a AES-256 session key /// 2. Encrypt the session key with the Imported /// RSA public key /// 3. Encode result above as base 64 string and export /// /// session key encrypted with receivers public key /// and encoded as a base 64 string internal string SafeExportSessionKey() { //generate one if not already done. GenerateSessionKey(); uint length = 0; // get key length first bool ret = PSCryptoNativeUtils.CryptExportKey(_hSessionKey, _hRSAKey, PSCryptoNativeUtils.SIMPLEBLOB, 0, null, ref length); CheckStatus(ret); // allocate buffer and export the key byte[] sessionkey = new byte[length]; ret = PSCryptoNativeUtils.CryptExportKey(_hSessionKey, _hRSAKey, PSCryptoNativeUtils.SIMPLEBLOB, 0, sessionkey, ref length); CheckStatus(ret); // now we can encrypt as we have the session key _canEncrypt = true; // convert the key to base64 before exporting return Convert.ToBase64String(sessionkey); } /// /// Import a public key into the provider whose context /// has been obtained /// /// base64 encoded public key to import internal void ImportPublicKeyFromBase64EncodedString(string publicKey) { Dbg.Assert(!string.IsNullOrEmpty(publicKey), "key cannot be null or empty"); byte[] convertedBase64 = Convert.FromBase64String(publicKey); bool ret = PSCryptoNativeUtils.CryptImportKey(_hProv, convertedBase64, convertedBase64.Length, PSSafeCryptKey.Zero, 0, ref _hRSAKey); CheckStatus(ret); } /// /// Import a session key from the remote side into /// the current CSP /// /// encrypted session key as a /// base64 encoded string internal void ImportSessionKeyFromBase64EncodedString(string sessionKey) { Dbg.Assert(!string.IsNullOrEmpty(sessionKey), "key cannot be null or empty"); byte[] convertedBase64 = Convert.FromBase64String(sessionKey); bool ret = PSCryptoNativeUtils.CryptImportKey(_hProv, convertedBase64, convertedBase64.Length, _hRSAKey, 0, ref _hSessionKey); CheckStatus(ret); // now we have imported the key and will be able to // encrypt using the session key _canEncrypt = true; } /// /// Encrypt the specified byte array /// /// data to encrypt /// encrypted byte array internal byte[] EncryptWithSessionKey(byte[] data) { // first make a copy of the original data.This is needed // as CryptEncrypt uses the same buffer to write the encrypted data // into. Dbg.Assert(_canEncrypt, "Remote key has not been imported to encrypt"); byte[] encryptedData = new byte[data.Length]; Array.Copy(data, 0, encryptedData, 0, data.Length); int dataLength = encryptedData.Length; // encryption always happens using the session key bool ret = PSCryptoNativeUtils.CryptEncrypt(_hSessionKey, IntPtr.Zero, true, 0, encryptedData, ref dataLength, data.Length); // if encryption failed, then dataLength will contain the length // of buffer needed to store the encrypted contents. Recreate // the buffer if (false == ret) { // before reallocating the encryptedData buffer, // zero out its contents for (int i = 0; i < encryptedData.Length; i++) { encryptedData[i] = 0; } encryptedData = new byte[dataLength]; Array.Copy(data, 0, encryptedData, 0, data.Length); dataLength = data.Length; ret = PSCryptoNativeUtils.CryptEncrypt(_hSessionKey, IntPtr.Zero, true, 0, encryptedData, ref dataLength, encryptedData.Length); CheckStatus(ret); } // make sure we copy only appropriate data // dataLength will contain the length of the encrypted // data buffer byte[] result = new byte[dataLength]; Array.Copy(encryptedData, 0, result, 0, dataLength); return result; } /// /// Decrypt the specified buffer /// /// data to decrypt /// decrypted buffer internal byte[] DecryptWithSessionKey(byte[] data) { // first make a copy of the original data.This is needed // as CryptDecrypt uses the same buffer to write the decrypted data // into. byte[] decryptedData = new byte[data.Length]; Array.Copy(data, 0, decryptedData, 0, data.Length); int dataLength = decryptedData.Length; bool ret = PSCryptoNativeUtils.CryptDecrypt(_hSessionKey, IntPtr.Zero, true, 0, decryptedData, ref dataLength); // if decryption failed, then dataLength will contain the length // of buffer needed to store the decrypted contents. Recreate // the buffer if (false == ret) { decryptedData = new byte[dataLength]; Array.Copy(data, 0, decryptedData, 0, data.Length); ret = PSCryptoNativeUtils.CryptDecrypt(_hSessionKey, IntPtr.Zero, true, 0, decryptedData, ref dataLength); CheckStatus(ret); } // make sure we copy only appropriate data // dataLength will contain the length of the encrypted // data buffer byte[] result = new byte[dataLength]; Array.Copy(decryptedData, 0, result, 0, dataLength); // zero out the decryptedData buffer for (int i = 0; i < decryptedData.Length; i++) { decryptedData[i] = 0; } return result; } /// /// Generates key pair in a thread safe manner /// the first time when required /// internal void GenerateKeyPair() { if (!s_keyPairGenerated) { lock (s_syncObject) { if (!s_keyPairGenerated) { s_hStaticProv = new PSSafeCryptProvHandle(); // We need PROV_RSA_AES to support AES-256 symmetric key // encryption. PROV_RSA_FULL supports only RC2 and RC4 bool ret = PSCryptoNativeUtils.CryptAcquireContext(ref s_hStaticProv, null, null, PSCryptoNativeUtils.PROV_RSA_AES, PSCryptoNativeUtils.CRYPT_VERIFYCONTEXT); CheckStatus(ret); s_hStaticRSAKey = new PSSafeCryptKey(); ret = PSCryptoNativeUtils.CryptGenKey(s_hStaticProv, PSCryptoNativeUtils.AT_KEYEXCHANGE, 0x08000000 | PSCryptoNativeUtils.CRYPT_EXPORTABLE, // key length -> 2048 ref s_hStaticRSAKey); CheckStatus(ret); // key needs to be generated once s_keyPairGenerated = true; } } } _hProv = s_hStaticProv; _hRSAKey = s_hStaticRSAKey; } /// /// Indicates if a key exchange is complete /// and this provider can encrypt /// internal bool CanEncrypt { get { return _canEncrypt; } set { _canEncrypt = value; } } #endregion Internal Methods #region Internal Static Methods /// /// Returns a crypto service provider for use in the /// client. This will reuse the key that has been /// generated /// /// crypto service provider for /// the client side internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForClient() { PSRSACryptoServiceProvider cryptoProvider = new PSRSACryptoServiceProvider(false); // set the handles for provider and rsa key cryptoProvider._hProv = s_hStaticProv; cryptoProvider._hRSAKey = s_hStaticRSAKey; return cryptoProvider; } /// /// Returns a crypto service provider for use in the /// server. This will not generate a key pair /// /// crypto service provider for /// the server side internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForServer() { PSRSACryptoServiceProvider cryptoProvider = new PSRSACryptoServiceProvider(true); return cryptoProvider; } #endregion Internal Static Methods #region Private Methods /// /// Checks the status of a call, if it had resulted in an error /// then obtains the last error, wraps it in an exception and /// throws the same /// /// value to examine private void CheckStatus(bool value) { if (value) { return; } uint errorCode = PSCryptoNativeUtils.GetLastError(); StringBuilder errorMessage = new StringBuilder(new ComponentModel.Win32Exception(unchecked((int)errorCode)).Message); throw new PSCryptoException(errorCode, errorMessage); } #endregion Private Methods #region IDisposable /// /// Dipose resources /// public void Dispose() { Dispose(true); System.GC.SuppressFinalize(this); } //[SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)] protected void Dispose(bool disposing) { if (disposing) { if (null != _hSessionKey) { if (!_hSessionKey.IsInvalid) { _hSessionKey.Dispose(); } _hSessionKey = null; } // we need to dismiss the provider and key // only if the static members are not allocated // since otherwise, these are just references // to the static members if (null == s_hStaticRSAKey) { if (null != _hRSAKey) { if (!_hRSAKey.IsInvalid) { _hRSAKey.Dispose(); } _hRSAKey = null; } } if (null == s_hStaticProv) { if (null != _hProv) { if (!_hProv.IsInvalid) { _hProv.Dispose(); } _hProv = null; } } } } /// /// Destructor /// ~PSRSACryptoServiceProvider() { // When Dispose() is called, GC.SuppressFinalize() // is called and therefore this finalizer will not // be invoked. Hence this is run only on process // shutdown Dispose(true); } #endregion IDisposable } /// /// Helper for exchanging keys and encrypting/decrypting /// secure strings for serialization in remoting /// internal abstract class PSRemotingCryptoHelper : IDisposable { #region Protected Members /// /// Crypto provider which will be used for importing remote /// public key as well as generating a session key, exporting /// it and performing symmetric key operations using the /// session key /// protected PSRSACryptoServiceProvider _rsaCryptoProvider; /// /// Key exchange has been completed and both keys /// available /// protected ManualResetEvent _keyExchangeCompleted = new ManualResetEvent(false); /// /// Object for synchronizing key exchange /// protected object syncObject = new object(); private bool _keyExchangeStarted = false; /// /// /// protected void RunKeyExchangeIfRequired() { Dbg.Assert(Session != null, "data structure handler not set"); if (!_rsaCryptoProvider.CanEncrypt) { try { lock (syncObject) { if (!_rsaCryptoProvider.CanEncrypt) { if (!_keyExchangeStarted) { _keyExchangeStarted = true; _keyExchangeCompleted.Reset(); Session.StartKeyExchange(); } } } } finally { // for whatever reason if StartKeyExchange() // throws an exception it should reset the // wait handle, so it should pass this wait // if it doesn't do so, its a bug _keyExchangeCompleted.WaitOne(); } } } /// /// Core logic to encrypt a string. Assumes session key is already generated /// /// /// secure string to be encrypted /// /// protected String EncryptSecureStringCore(SecureString secureString) { String encryptedDataAsString = null; if (_rsaCryptoProvider.CanEncrypt) { IntPtr ptr = ClrFacade.SecureStringToCoTaskMemUnicode(secureString); if (ptr != IntPtr.Zero) { byte[] data = new byte[secureString.Length * 2]; for (int i = 0; i < data.Length; i++) { data[i] = Marshal.ReadByte(ptr, i); } Marshal.ZeroFreeCoTaskMemUnicode(ptr); try { byte[] encryptedData = _rsaCryptoProvider.EncryptWithSessionKey(data); encryptedDataAsString = Convert.ToBase64String(encryptedData); } finally { for (int j = 0; j < data.Length; j++) { data[j] = 0; } } } // if (ptr ... } // if (rsa != null... else { Dbg.Assert(false, "Session key not availble to encrypt secure string"); } return encryptedDataAsString; } /// /// Core logic to decrypt a secure string. Assumes session key is already available /// /// /// encrypted string to be decrypted /// /// protected SecureString DecryptSecureStringCore(String encryptedString) { // removing an earlier assert from here. It is // possible to encrypt and decrypt empty // secure strings SecureString secureString = null; // before you can decrypt a key exchange should have // happened successfully if (_rsaCryptoProvider.CanEncrypt) { byte[] data = null; try { data = Convert.FromBase64String(encryptedString); } catch (FormatException) { // do nothing // this catch is to ensure that the exception doesn't // go unhandled leading to a crash throw new PSCryptoException(); } if (data != null) { byte[] decryptedData = _rsaCryptoProvider.DecryptWithSessionKey(data); secureString = new SecureString(); UInt16 value = 0; try { for (int i = 0; i < decryptedData.Length; i += 2) { value = (UInt16)(decryptedData[i] + (UInt16)(decryptedData[i + 1] << 8)); secureString.AppendChar((char)value); value = 0; } } finally { // if there was an exception for whatever reason, // clear the last value store in Value value = 0; // zero out the contents for (int i = 0; i < decryptedData.Length; i += 2) { decryptedData[i] = 0; decryptedData[i + 1] = 0; } } } } else { Dbg.Assert(false, "Session key not available to decrypt"); } return secureString; } #endregion Protected Members #region Internal Methods /// /// Encrypt a secure string /// /// secure string to encrypt /// encrypted string /// This method zeroes out all interim buffers used internal abstract String EncryptSecureString(SecureString secureString); /// /// Decrypt a string and construct a secure string from its /// contents /// /// encrypted string /// secure string object /// This method zeroes out any interim buffers used internal abstract SecureString DecryptSecureString(String encryptedString); /// /// Represents the session to be used for requesting public key /// internal abstract RemoteSession Session { get; set; } /// /// /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// /// /// public void Dispose(bool disposing) { if (disposing) { if (_rsaCryptoProvider != null) { _rsaCryptoProvider.Dispose(); } _rsaCryptoProvider = null; _keyExchangeCompleted.Dispose(); } } /// /// Resets the wait for key exchange /// internal void CompleteKeyExchange() { _keyExchangeCompleted.Set(); } #endregion Internal Methods } /// /// Helper for exchanging keys and encrypting/decrypting /// secure strings for serialization in remoting /// internal class PSRemotingCryptoHelperServer : PSRemotingCryptoHelper { #region Private Members /// /// This is the instance of runspace pool data structure handler /// to use for negotiations /// private RemoteSession _session; #endregion Private Members #region Constructors /// /// Creates the encryption provider, but generates no key. /// The key will be imported later /// internal PSRemotingCryptoHelperServer() { _rsaCryptoProvider = PSRSACryptoServiceProvider.GetRSACryptoServiceProviderForServer(); } #endregion Constructors #region Internal Methods internal override string EncryptSecureString(SecureString secureString) { ServerRemoteSession session = Session as ServerRemoteSession; //session!=null check required for DRTs TestEncryptSecureString* entries in CryptoUtilsTest/UTUtils.dll //for newer clients, server will never initiate key exchange. //for server, just the session key is required to encrypt/decrypt anything if ((session != null) && (session.Context.ClientCapability.ProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM)) { _rsaCryptoProvider.GenerateSessionKey(); } else //older clients { RunKeyExchangeIfRequired(); } return EncryptSecureStringCore(secureString); } internal override SecureString DecryptSecureString(string encryptedString) { RunKeyExchangeIfRequired(); return DecryptSecureStringCore(encryptedString); } /// /// Imports a public key from its base64 encoded string representation /// /// public key in its string representation /// true on success internal bool ImportRemotePublicKey(String publicKeyAsString) { Dbg.Assert(!String.IsNullOrEmpty(publicKeyAsString), "public key passed in cannot be null"); // generate the crypto provider to use for encryption //_rsaCryptoProvider = GenerateCryptoServiceProvider(false); try { _rsaCryptoProvider.ImportPublicKeyFromBase64EncodedString(publicKeyAsString); } catch (PSCryptoException) { return false; } return true; } /// /// Represents the session to be used for requesting public key /// internal override RemoteSession Session { get { return _session; } set { _session = value; } } /// /// /// /// /// internal bool ExportEncryptedSessionKey(out string encryptedSessionKey) { try { encryptedSessionKey = _rsaCryptoProvider.SafeExportSessionKey(); } catch (PSCryptoException) { encryptedSessionKey = String.Empty; return false; } return true; } /// /// Gets a helper with a test session /// /// helper for testing /// To be used only for testing internal static PSRemotingCryptoHelperServer GetTestRemotingCryptHelperServer() { PSRemotingCryptoHelperServer helper = new PSRemotingCryptoHelperServer(); helper.Session = new TestHelperSession(); return helper; } #endregion Internal Methods } /// /// Helper for exchanging keys and encrypting/decrypting /// secure strings for serialization in remoting /// internal class PSRemotingCryptoHelperClient : PSRemotingCryptoHelper { #region Private Members #endregion Private Members #region Constructors /// /// Creates the encryption provider, but generates no key. /// The key will be imported later /// internal PSRemotingCryptoHelperClient() { _rsaCryptoProvider = PSRSACryptoServiceProvider.GetRSACryptoServiceProviderForClient(); //_session = new RemoteSession(); } #endregion Constructors #region Protected Methods #endregion Protected Methods #region Internal Methods internal override string EncryptSecureString(SecureString secureString) { RunKeyExchangeIfRequired(); return EncryptSecureStringCore(secureString); } internal override SecureString DecryptSecureString(string encryptedString) { RunKeyExchangeIfRequired(); return DecryptSecureStringCore(encryptedString); } /// /// Export the public key as a base64 encoded string /// /// on execution will contain /// the public key as string /// true on success internal bool ExportLocalPublicKey(out String publicKeyAsString) { // generate keys - the method already takes of creating // only when its not already created try { _rsaCryptoProvider.GenerateKeyPair(); } catch (PSCryptoException) { throw; // the caller has to ensure that they // complete the key exchange process } try { publicKeyAsString = _rsaCryptoProvider.GetPublicKeyAsBase64EncodedString(); } catch (PSCryptoException) { publicKeyAsString = String.Empty; return false; } return true; } /// /// /// /// /// internal bool ImportEncryptedSessionKey(string encryptedSessionKey) { Dbg.Assert(!String.IsNullOrEmpty(encryptedSessionKey), "encrypted sessoin key passed in cannot be null"); try { _rsaCryptoProvider.ImportSessionKeyFromBase64EncodedString(encryptedSessionKey); } catch (PSCryptoException) { return false; } return true; } /// /// Represents the session to be used for requesting public key /// internal override RemoteSession Session { get; set; } /// /// Gets a helper with a test session /// /// helper for testing /// To be used only for testing internal static PSRemotingCryptoHelperClient GetTestRemotingCryptHelperClient() { PSRemotingCryptoHelperClient helper = new PSRemotingCryptoHelperClient(); helper.Session = new TestHelperSession(); return helper; } #endregion Internal Methods } #region TestHelpers internal class TestHelperSession : RemoteSession { internal override void StartKeyExchange() { // intentionally left blank } internal override RemotingDestination MySelf { get { return RemotingDestination.InvalidDestination; } } internal override void CompleteKeyExchange() { // intentionally left blank } } #endregion TestHelpers }