MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Diagnostics.CodeAnalysis;5using System.IO;6using System.Linq;7using System.Management.Automation.Remoting;8using System.Runtime.Serialization;9using System.Security;10using System.Security.Cryptography;11using System.Text;12using System.Threading;13 14using Dbg = System.Management.Automation.Diagnostics;15 16namespace System.Management.Automation.Internal17{18 /// <summary>19 /// This class provides the converters for all Native CAPI key blob formats.20 /// </summary>21 internal static class PSCryptoNativeConverter22 {23 #region Constants24 25 /// <summary>26 /// The blob version is fixed.27 /// </summary>28 public const uint CUR_BLOB_VERSION = 0x00000002;29 30 /// <summary>31 /// RSA Key.32 /// </summary>33 public const uint CALG_RSA_KEYX = 0x000000a4;34 35 /// <summary>36 /// AES 256 symmetric key.37 /// </summary>38 public const uint CALG_AES_256 = 0x00000010;39 40 /// <summary>41 /// Option for exporting public key blob.42 /// </summary>43 public const uint PUBLICKEYBLOB = 0x00000006;44 45 /// <summary>46 /// PUBLICKEYBLOB header length.47 /// </summary>48 public const int PUBLICKEYBLOB_HEADER_LEN = 20;49 50 /// <summary>51 /// Option for exporting a session key.52 /// </summary>53 public const uint SIMPLEBLOB = 0x00000001;54 55 /// <summary>56 /// SIMPLEBLOB header length.57 /// </summary>58 public const int SIMPLEBLOB_HEADER_LEN = 12;59 60 #endregion Constants61 62 #region Functions63 64 private static int ToInt32LE(byte[] bytes, int offset)65 {66 return (bytes[offset + 3] << 24) | (bytes[offset + 2] << 16) | (bytes[offset + 1] << 8) | bytes[offset];67 }68 69 private static uint ToUInt32LE(byte[] bytes, int offset)70 {71 return (uint)((bytes[offset + 3] << 24) | (bytes[offset + 2] << 16) | (bytes[offset + 1] << 8) | bytes[offset]);72 }73 74 private static byte[] GetBytesLE(int val)75 {76 return new[] {77 (byte)(val & 0xff),78 (byte)((val >> 8) & 0xff),79 (byte)((val >> 16) & 0xff),80 (byte)((val >> 24) & 0xff)81 };82 }83 84 private static byte[] CreateReverseByteArray(byte[] data)85 {86 byte[] reverseData = new byte[data.Length];87 Array.Copy(data, reverseData, data.Length);88 Array.Reverse(reverseData);89 return reverseData;90 }91 92 internal static RSA FromCapiPublicKeyBlob(byte[] blob)93 {94 return FromCapiPublicKeyBlob(blob, 0);95 }96 97 private static RSA FromCapiPublicKeyBlob(byte[] blob, int offset)98 {99 ArgumentNullException.ThrowIfNull(blob);100 101 if (offset > blob.Length)102 {103 throw new ArgumentException(SecuritySupportStrings.InvalidOffset);104 }105 106 var rsap = GetParametersFromCapiPublicKeyBlob(blob, offset);107 108 try109 {110 RSA rsa = RSA.Create();111 rsa.ImportParameters(rsap);112 return rsa;113 }114 catch (Exception ex)115 {116 throw new CryptographicException(SecuritySupportStrings.CannotImportPublicKey, ex);117 }118 }119 120 private static RSAParameters GetParametersFromCapiPublicKeyBlob(byte[] blob, int offset)121 {122 ArgumentNullException.ThrowIfNull(blob);123 124 if (offset > blob.Length)125 {126 throw new ArgumentException(SecuritySupportStrings.InvalidOffset);127 }128 129 if (blob.Length < PUBLICKEYBLOB_HEADER_LEN)130 {131 throw new ArgumentException(SecuritySupportStrings.InvalidPublicKey);132 }133 134 try135 {136 if ((blob[offset] != PUBLICKEYBLOB) || // PUBLICKEYBLOB (0x06)137 (blob[offset + 1] != CUR_BLOB_VERSION) || // Version (0x02)138 (blob[offset + 2] != 0x00) || // Reserved (word)139 (blob[offset + 3] != 0x00) ||140 (ToUInt32LE(blob, offset + 8) != 0x31415352)) // DWORD magic = RSA1141 {142 throw new CryptographicException(SecuritySupportStrings.InvalidPublicKey);143 }144 145 // DWORD bitlen146 int bitLen = ToInt32LE(blob, offset + 12);147 148 // DWORD public exponent149 RSAParameters rsap = new RSAParameters();150 rsap.Exponent = new byte[3];151 rsap.Exponent[0] = blob[offset + 18];152 rsap.Exponent[1] = blob[offset + 17];153 rsap.Exponent[2] = blob[offset + 16];154 155 int pos = offset + 20;156 int byteLen = (bitLen >> 3);157 rsap.Modulus = new byte[byteLen];158 Buffer.BlockCopy(blob, pos, rsap.Modulus, 0, byteLen);159 Array.Reverse(rsap.Modulus);160 161 return rsap;162 }163 catch (Exception ex)164 {165 throw new CryptographicException(SecuritySupportStrings.InvalidPublicKey, ex);166 }167 }168 169 internal static byte[] ToCapiPublicKeyBlob(RSA rsa)170 {171 ArgumentNullException.ThrowIfNull(rsa);172 173 RSAParameters p = rsa.ExportParameters(false);174 int keyLength = p.Modulus.Length; // in bytes175 byte[] blob = new byte[PUBLICKEYBLOB_HEADER_LEN + keyLength];176 177 blob[0] = (byte)PUBLICKEYBLOB; // Type - PUBLICKEYBLOB (0x06)178 blob[1] = (byte)CUR_BLOB_VERSION; // Version - Always CUR_BLOB_VERSION (0x02)179 // [2], [3] // RESERVED - Always 0180 blob[5] = (byte)CALG_RSA_KEYX; // ALGID - Always 00 a4 00 00 (for CALG_RSA_KEYX)181 blob[8] = 0x52; // Magic - RSA1 (ASCII in hex)182 blob[9] = 0x53;183 blob[10] = 0x41;184 blob[11] = 0x31;185 186 byte[] bitlen = GetBytesLE(keyLength << 3);187 blob[12] = bitlen[0]; // bitlen188 blob[13] = bitlen[1];189 blob[14] = bitlen[2];190 blob[15] = bitlen[3];191 192 // public exponent (DWORD)193 int pos = 16;194 int n = p.Exponent.Length;195 196 Dbg.Assert(n <= 4, "RSA exponent byte length cannot exceed allocated segment");197 198 while (n > 0)199 {200 blob[pos++] = p.Exponent[--n];201 }202 203 // modulus204 pos = 20;205 byte[] key = p.Modulus;206 Array.Reverse(key);207 Buffer.BlockCopy(key, 0, blob, pos, keyLength);208 209 return blob;210 }211 212 internal static byte[] FromCapiSimpleKeyBlob(byte[] blob)213 {214 ArgumentNullException.ThrowIfNull(blob);215 216 if (blob.Length < SIMPLEBLOB_HEADER_LEN)217 {218 throw new ArgumentException(SecuritySupportStrings.InvalidSessionKey);219 }220 221 // just ignore the header of the capi blob and go straight for the key222 return CreateReverseByteArray(blob.Skip(SIMPLEBLOB_HEADER_LEN).ToArray());223 }224 225 internal static byte[] ToCapiSimpleKeyBlob(byte[] encryptedKey)226 {227 ArgumentNullException.ThrowIfNull(encryptedKey);228 229 // formulate the PUBLICKEYSTRUCT230 byte[] blob = new byte[SIMPLEBLOB_HEADER_LEN + encryptedKey.Length];231 232 blob[0] = (byte)SIMPLEBLOB; // Type - SIMPLEBLOB (0x01)233 blob[1] = (byte)CUR_BLOB_VERSION; // Version - Always CUR_BLOB_VERSION (0x02)234 // [2], [3] // RESERVED - Always 0235 blob[4] = (byte)CALG_AES_256; // AES-256 algo id (0x10)236 blob[5] = 0x66; // ??237 // [6], [7], [8] // 0x00238 blob[9] = (byte)CALG_RSA_KEYX; // 0xa4239 // [10], [11] // 0x00240 241 // create a reversed copy and add the encrypted key242 byte[] reversedKey = CreateReverseByteArray(encryptedKey);243 Buffer.BlockCopy(reversedKey, 0, blob, SIMPLEBLOB_HEADER_LEN, reversedKey.Length);244 245 return blob;246 }247 248 #endregion Functions249 }250 251 /// <summary>252 /// Defines a custom exception which is thrown when253 /// a native CAPI call results in an error.254 /// </summary>255 /// <remarks>This exception is currently internal as it's not256 /// surfaced to the user. However, if we decide to surface errors257 /// to the user when something fails on the remote end, then this258 /// can be turned public</remarks>259 [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")]260 internal class PSCryptoException : Exception261 {262 #region Private Members263 264 private readonly uint _errorCode;265 266 #endregion Private Members267 268 #region Internal Properties269 270 /// <summary>271 /// Error code returned by the native CAPI call.272 /// </summary>273 internal uint ErrorCode274 {275 get276 {277 return _errorCode;278 }279 }280 281 #endregion Internal Properties282 283 #region Constructors284 285 /// <summary>286 /// Default constructor.287 /// </summary>288 public PSCryptoException()289 : this(0, new StringBuilder(string.Empty)) { }290 291 /// <summary>292 /// Constructor that will be used from within CryptoUtils.293 /// </summary>294 /// <param name="errorCode">error code returned by native295 /// crypto application</param>296 /// <param name="message">Error message associated with this failure.</param>297 public PSCryptoException(uint errorCode, StringBuilder message)298 : base(message.ToString())299 {300 _errorCode = errorCode;301 }302 303 /// <summary>304 /// Constructor with just message but no inner exception.305 /// </summary>306 /// <param name="message">Error message associated with this failure.</param>307 public PSCryptoException(string message)308 : this(message, null) { }309 310 /// <summary>311 /// Constructor with inner exception.312 /// </summary>313 /// <param name="message">Error message.</param>314 /// <param name="innerException">Inner exception.</param>315 /// <remarks>This constructor is currently not called316 /// explicitly from crypto utils</remarks>317 public PSCryptoException(string message, Exception innerException)318 : base(message, innerException)319 {320 _errorCode = unchecked((uint)-1);321 }322 323 /// <summary>324 /// Constructor which has type specific serialization logic.325 /// </summary>326 /// <param name="info">Serialization info.</param>327 /// <param name="context">Context in which this constructor is called.</param>328 /// <remarks>Currently no custom type-specific serialization logic is329 /// implemented</remarks>330 [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")]331 protected PSCryptoException(SerializationInfo info, StreamingContext context)332 {333 throw new NotSupportedException();334 }335 336 #endregion Constructors337 }338 339 /// <summary>340 /// A reverse compatible implementation of session key exchange. This supports the CAPI341 /// keyblob formats but uses dotnet std abstract AES and RSA classes for all crypto operations.342 /// </summary>343 internal sealed class PSRSACryptoServiceProvider : IDisposable344 {345 #region Private Members346 347 // handle session key encryption/decryption348 private RSA _rsa;349 350 // handle to the AES provider object (houses session key and iv)351 private readonly Aes _aes;352 353 // this flag indicates that this class has a key imported from the354 // remote end and so can be used for encryption355 private bool _canEncrypt;356 357 // bool indicating if session key was generated before358 private bool _sessionKeyGenerated = false;359 360 private static readonly object s_syncObject = new object();361 362 #endregion Private Members363 364 #region Constructors365 366 /// <summary>367 /// Private constructor.368 /// </summary>369 /// <param name="serverMode">indicates if this service370 /// provider is operating in server mode</param>371 private PSRSACryptoServiceProvider(bool serverMode)372 {373 if (serverMode)374 {375 GenerateKeyPair();376 }377 378 _aes = Aes.Create();379 _aes.IV = new byte[16]; // iv should be 0380 }381 382 #endregion Constructors383 384 #region Internal Methods385 386 /// <summary>387 /// Get the public key, in CAPI-compatible form, as a base64 encoded string.388 /// </summary>389 /// <returns>Public key as base64 encoded string.</returns>390 internal string GetPublicKeyAsBase64EncodedString()391 {392 Dbg.Assert(_rsa != null, "No public key available.");393 394 byte[] capiPublicKeyBlob = PSCryptoNativeConverter.ToCapiPublicKeyBlob(_rsa);395 396 return Convert.ToBase64String(capiPublicKeyBlob);397 }398 399 /// <summary>400 /// Generates an AEX-256 session key if one is not already generated.401 /// </summary>402 internal void GenerateSessionKey()403 {404 if (_sessionKeyGenerated)405 return;406 407 lock (s_syncObject)408 {409 if (!_sessionKeyGenerated)410 {411 // Aes object gens key automatically on construction, so this is somewhat redundant,412 // but at least the actionable key will not be in-memory until it's requested fwiw.413 _aes.GenerateKey();414 _sessionKeyGenerated = true;415 _canEncrypt = true; // we can encrypt and decrypt once session key is available416 }417 }418 }419 420 /// <summary>421 /// 1. Generate a AES-256 session key422 /// 2. Encrypt the session key with the Imported423 /// RSA public key424 /// 3. Encode result above as base 64 string and export.425 /// </summary>426 /// <returns>Session key encrypted with receivers public key427 /// and encoded as a base 64 string.</returns>428 internal string SafeExportSessionKey()429 {430 Dbg.Assert(_rsa != null, "No public key available.");431 432 // generate one if not already done.433 GenerateSessionKey();434 435 // encrypt it436 // codeql[cs/cryptography/rsa-unapproved-encryption-padding-scheme] - PowerShell v7.4 and later versions have deprecated the key exchange in the remoting protocol. This code is kept only for backward compatibility reason.437 byte[] encryptedKey = _rsa.Encrypt(_aes.Key, RSAEncryptionPadding.Pkcs1);438 439 // convert the key to capi simpleblob format before exporting440 byte[] simpleKeyBlob = PSCryptoNativeConverter.ToCapiSimpleKeyBlob(encryptedKey);441 return Convert.ToBase64String(simpleKeyBlob);442 }443 444 /// <summary>445 /// Import a public key into the provider whose context446 /// has been obtained.447 /// </summary>448 /// <param name="publicKey">Base64 encoded public key to import.</param>449 internal void ImportPublicKeyFromBase64EncodedString(string publicKey)450 {451 Dbg.Assert(!string.IsNullOrEmpty(publicKey), "key cannot be null or empty");452 453 byte[] publicKeyBlob = Convert.FromBase64String(publicKey);454 _rsa = PSCryptoNativeConverter.FromCapiPublicKeyBlob(publicKeyBlob);455 }456 457 /// <summary>458 /// Import a session key from the remote side into459 /// the current CSP.460 /// </summary>461 /// <param name="sessionKey">encrypted session key as a462 /// base64 encoded string</param>463 internal void ImportSessionKeyFromBase64EncodedString(string sessionKey)464 {465 Dbg.Assert(!string.IsNullOrEmpty(sessionKey), "key cannot be null or empty");466 467 byte[] sessionKeyBlob = Convert.FromBase64String(sessionKey);468 byte[] rsaEncryptedKey = PSCryptoNativeConverter.FromCapiSimpleKeyBlob(sessionKeyBlob);469 470 // codeql[cs/cryptography/rsa-unapproved-encryption-padding-scheme] - PowerShell v7.4 and later versions have deprecated the key exchange in the remoting protocol. This code is kept only for backward compatibility reason.471 _aes.Key = _rsa.Decrypt(rsaEncryptedKey, RSAEncryptionPadding.Pkcs1);472 473 // now we have imported the key and will be able to474 // encrypt using the session key475 _canEncrypt = true;476 }477 478 /// <summary>479 /// Encrypt the specified byte array.480 /// </summary>481 /// <param name="data">Data to encrypt.</param>482 /// <returns>Encrypted byte array.</returns>483 internal byte[] EncryptWithSessionKey(byte[] data)484 {485 Dbg.Assert(_canEncrypt, "Remote key has not been imported to encrypt");486 487 using (ICryptoTransform encryptor = _aes.CreateEncryptor())488 using (MemoryStream targetStream = new MemoryStream())489 using (MemoryStream sourceStream = new MemoryStream(data))490 {491 using (CryptoStream cryptoStream = new CryptoStream(targetStream, encryptor, CryptoStreamMode.Write))492 {493 sourceStream.CopyTo(cryptoStream);494 }495 496 return targetStream.ToArray();497 }498 }499 500 /// <summary>501 /// Decrypt the specified buffer.502 /// </summary>503 /// <param name="data">Data to decrypt.</param>504 /// <returns>Decrypted buffer.</returns>505 internal byte[] DecryptWithSessionKey(byte[] data)506 {507 using (ICryptoTransform decryptor = _aes.CreateDecryptor())508 using (MemoryStream sourceStream = new MemoryStream(data))509 using (MemoryStream targetStream = new MemoryStream())510 {511 using (CryptoStream csDecrypt = new CryptoStream(sourceStream, decryptor, CryptoStreamMode.Read))512 {513 csDecrypt.CopyTo(targetStream);514 }515 516 return targetStream.ToArray();517 }518 }519 520 /// <summary>521 /// Generates key pair in a thread safe manner522 /// the first time when required.523 /// </summary>524 internal void GenerateKeyPair()525 {526 _rsa = RSA.Create();527 _rsa.KeySize = 2048;528 }529 530 /// <summary>531 /// Indicates if a key exchange is complete532 /// and this provider can encrypt.533 /// </summary>534 internal bool CanEncrypt535 {536 get537 {538 return _canEncrypt;539 }540 541 set542 {543 _canEncrypt = value;544 }545 }546 547 #endregion Internal Methods548 549 #region Internal Static Methods550 551 /// <summary>552 /// Returns a crypto service provider for use in the553 /// client. This will reuse the key that has been554 /// generated.555 /// </summary>556 /// <returns>Crypto service provider for557 /// the client side.</returns>558 internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForClient()559 {560 return new PSRSACryptoServiceProvider(false);561 }562 563 /// <summary>564 /// Returns a crypto service provider for use in the565 /// server. This will not generate a key pair.566 /// </summary>567 /// <returns>Crypto service provider for568 /// the server side.</returns>569 internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForServer()570 {571 return new PSRSACryptoServiceProvider(true);572 }573 574 #endregion Internal Static Methods575 576 #region IDisposable577 578 /// <summary>579 /// Release all resources.580 /// </summary>581 public void Dispose()582 {583 _rsa?.Dispose();584 _aes?.Dispose();585 }586 587 #endregion IDisposable588 }589 590 /// <summary>591 /// Helper for exchanging keys and encrypting/decrypting592 /// secure strings for serialization in remoting.593 /// </summary>594 public abstract class PSRemotingCryptoHelper : IDisposable595 {596 #region Protected Members597 598 /// <summary>599 /// Crypto provider which will be used for importing remote600 /// public key as well as generating a session key, exporting601 /// it and performing symmetric key operations using the602 /// session key.603 /// </summary>604 internal PSRSACryptoServiceProvider _rsaCryptoProvider;605 606 /// <summary>607 /// Key exchange has been completed and both keys608 /// available.609 /// </summary>610 protected ManualResetEvent _keyExchangeCompleted = new ManualResetEvent(false);611 612 /// <summary>613 /// Object for synchronizing key exchange.614 /// </summary>615 protected object syncObject = new object();616 617 private bool _keyExchangeStarted = false;618 619 /// <summary>620 /// </summary>621 protected void RunKeyExchangeIfRequired()622 {623 Dbg.Assert(Session != null, "data structure handler not set");624 625 if (!_rsaCryptoProvider.CanEncrypt)626 {627 try628 {629 lock (syncObject)630 {631 if (!_rsaCryptoProvider.CanEncrypt)632 {633 if (!_keyExchangeStarted)634 {635 _keyExchangeStarted = true;636 _keyExchangeCompleted.Reset();637 Session.StartKeyExchange();638 }639 }640 }641 }642 finally643 {644 // for whatever reason if StartKeyExchange()645 // throws an exception it should reset the646 // wait handle, so it should pass this wait647 // if it doesn't do so, its a bug648 _keyExchangeCompleted.WaitOne();649 }650 }651 }652 653 /// <summary>654 /// Gets the bytes of a secure string.655 /// </summary>656 private static byte[] GetBytesFromSecureString(SecureString secureString)657 {658 return secureString is null659 ? null660 : Microsoft.PowerShell.SecureStringHelper.GetData(secureString);661 }662 663 /// <summary>664 /// Gets a secure string from the specified byte array.665 /// </summary>666 private static SecureString GetSecureStringFromBytes(byte[] data)667 {668 Dbg.Assert(data is not null, "The passed-in data cannot be null.");669 670 try671 {672 return Microsoft.PowerShell.SecureStringHelper.New(data);673 }674 finally675 {676 // zero out the contents677 Array.Clear(data);678 }679 }680 681 /// <summary>682 /// Convert a secure string to a base64 encoded string.683 /// </summary>684 protected string ConvertSecureStringToBase64String(SecureString secureString)685 {686 string dataAsString = null;687 byte[] data = GetBytesFromSecureString(secureString);688 689 if (data is not null)690 {691 try692 {693 dataAsString = Convert.ToBase64String(data);694 }695 finally696 {697 Array.Clear(data);698 }699 }700 701 return dataAsString;702 }703 704 /// <summary>705 /// Convert a base64 encoded string to a secure string.706 /// </summary>707 /// <param name="base64String"></param>708 /// <returns></returns>709 protected SecureString ConvertBase64StringToSecureString(string base64String)710 {711 try712 {713 byte[] data = Convert.FromBase64String(base64String);714 return GetSecureStringFromBytes(data);715 }716 catch (FormatException)717 {718 // do nothing719 // this catch is to ensure that the exception doesn't720 // go unhandled leading to a crash721 throw new PSCryptoException();722 }723 }724 725 /// <summary>726 /// Core logic to encrypt a string. Assumes session key is already generated.727 /// </summary>728 /// <param name="secureString">729 /// secure string to be encrypted730 /// </param>731 /// <returns></returns>732 protected string EncryptSecureStringCore(SecureString secureString)733 {734 string encryptedDataAsString = null;735 736 if (_rsaCryptoProvider.CanEncrypt)737 {738 byte[] data = GetBytesFromSecureString(secureString);739 740 if (data is not null)741 {742 try743 {744 byte[] encryptedData = _rsaCryptoProvider.EncryptWithSessionKey(data);745 encryptedDataAsString = Convert.ToBase64String(encryptedData);746 }747 finally748 {749 Array.Clear(data);750 }751 }752 }753 else754 {755 throw new PSCryptoException(SecuritySupportStrings.CannotEncryptSecureString);756 }757 758 return encryptedDataAsString;759 }760 761 /// <summary>762 /// Core logic to decrypt a secure string. Assumes session key is already available.763 /// </summary>764 /// <param name="encryptedString">765 /// encrypted string to be decrypted766 /// </param>767 /// <returns></returns>768 protected SecureString DecryptSecureStringCore(string encryptedString)769 {770 // removing an earlier assert from here. It is771 // possible to encrypt and decrypt empty772 // secure strings773 SecureString secureString = null;774 775 // before you can decrypt a key exchange should have776 // happened successfully777 if (_rsaCryptoProvider.CanEncrypt)778 {779 try780 {781 byte[] data = Convert.FromBase64String(encryptedString);782 byte[] decryptedData = _rsaCryptoProvider.DecryptWithSessionKey(data);783 secureString = GetSecureStringFromBytes(decryptedData);784 }785 catch (FormatException)786 {787 // do nothing788 // this catch is to ensure that the exception doesn't789 // go unhandled leading to a crash790 throw new PSCryptoException();791 }792 }793 else794 {795 Dbg.Assert(false, "Session key not available to decrypt");796 }797 798 return secureString;799 }800 801 #endregion Protected Members802 803 #region Internal Methods804 805 /// <summary>806 /// Encrypt a secure string.807 /// </summary>808 /// <param name="secureString">Secure string to encrypt.</param>809 /// <returns>Encrypted string.</returns>810 /// <remarks>This method zeroes out all interim buffers used</remarks>811 internal abstract string EncryptSecureString(SecureString secureString);812 813 /// <summary>814 /// Decrypt a string and construct a secure string from its815 /// contents.816 /// </summary>817 /// <param name="encryptedString">Encrypted string.</param>818 /// <returns>Secure string object.</returns>819 /// <remarks>This method zeroes out any interim buffers used</remarks>820 internal abstract SecureString DecryptSecureString(string encryptedString);821 822 /// <summary>823 /// Represents the session to be used for requesting public key.824 /// </summary>825 internal abstract RemoteSession Session { get; set; }826 827 /// <summary>828 /// </summary>829 public void Dispose()830 {831 Dispose(true);832 GC.SuppressFinalize(this);833 }834 835 /// <summary>836 /// </summary>837 /// <param name="disposing"></param>838 public void Dispose(bool disposing)839 {840 if (disposing)841 {842 _rsaCryptoProvider?.Dispose();843 _rsaCryptoProvider = null;844 845 _keyExchangeCompleted.Dispose();846 }847 }848 849 /// <summary>850 /// Resets the wait for key exchange.851 /// </summary>852 internal void CompleteKeyExchange()853 {854 _keyExchangeCompleted.Set();855 }856 857 #endregion Internal Methods858 }859 860 /// <summary>861 /// Helper for exchanging keys and encrypting/decrypting862 /// secure strings for serialization in remoting.863 /// </summary>864 internal class PSRemotingCryptoHelperServer : PSRemotingCryptoHelper865 {866 #region Private Members867 868 /// <summary>869 /// This is the instance of runspace pool data structure handler870 /// to use for negotiations.871 /// </summary>872 private RemoteSession _session;873 874 #endregion Private Members875 876 #region Constructors877 878 /// <summary>879 /// Creates the encryption provider, but generates no key.880 /// The key will be imported later.881 /// </summary>882 internal PSRemotingCryptoHelperServer()883 {884 _rsaCryptoProvider = PSRSACryptoServiceProvider.GetRSACryptoServiceProviderForServer();885 }886 887 #endregion Constructors888 889 #region Internal Methods890 891 internal override string EncryptSecureString(SecureString secureString)892 {893 // session!=null check required for DRTs TestEncryptSecureString* entries in CryptoUtilsTest/UTUtils.dll894 bool initiateKeyExchange = true;895 896 if (Session is ServerRemoteSession session)897 {898 Version clientProtocolVersion = session.Context.ClientCapability.ProtocolVersion;899 if (clientProtocolVersion >= RemotingConstants.ProtocolVersion_2_4)900 {901 // For client v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing.902 return ConvertSecureStringToBase64String(secureString);903 }904 905 if (clientProtocolVersion >= RemotingConstants.ProtocolVersion_2_2)906 {907 // For client v2.2+, server will never initiate key exchange.908 // For server, just the session key is required to encrypt/decrypt anything909 initiateKeyExchange = false;910 _rsaCryptoProvider.GenerateSessionKey();911 }912 }913 914 if (initiateKeyExchange)915 {916 // older clients.917 RunKeyExchangeIfRequired();918 }919 920 return EncryptSecureStringCore(secureString);921 }922 923 internal override SecureString DecryptSecureString(string encryptedString)924 {925 if (Session is ServerRemoteSession session && session.Context.ClientCapability.ProtocolVersion >= RemotingConstants.ProtocolVersion_2_4)926 {927 // For client v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing.928 return ConvertBase64StringToSecureString(encryptedString);929 }930 931 RunKeyExchangeIfRequired();932 933 return DecryptSecureStringCore(encryptedString);934 }935 936 /// <summary>937 /// Imports a public key from its base64 encoded string representation.938 /// </summary>939 /// <param name="publicKeyAsString">Public key in its string representation.</param>940 /// <returns>True on success.</returns>941 internal bool ImportRemotePublicKey(string publicKeyAsString)942 {943 Dbg.Assert(!string.IsNullOrEmpty(publicKeyAsString), "public key passed in cannot be null");944 945 // generate the crypto provider to use for encryption946 // _rsaCryptoProvider = GenerateCryptoServiceProvider(false);947 948 try949 {950 _rsaCryptoProvider.ImportPublicKeyFromBase64EncodedString(publicKeyAsString);951 }952 catch (PSCryptoException)953 {954 return false;955 }956 957 return true;958 }959 960 /// <summary>961 /// Represents the session to be used for requesting public key.962 /// </summary>963 internal override RemoteSession Session964 {965 get966 {967 return _session;968 }969 970 set971 {972 _session = value;973 }974 }975 976 /// <summary>977 /// </summary>978 /// <param name="encryptedSessionKey"></param>979 /// <returns></returns>980 internal bool ExportEncryptedSessionKey(out string encryptedSessionKey)981 {982 try983 {984 encryptedSessionKey = _rsaCryptoProvider.SafeExportSessionKey();985 }986 catch (PSCryptoException)987 {988 encryptedSessionKey = string.Empty;989 return false;990 }991 992 return true;993 }994 995 /// <summary>996 /// Gets a helper with a test session.997 /// </summary>998 /// <returns>Helper for testing.</returns>999 /// <remarks>To be used only for testing</remarks>1000 internal static PSRemotingCryptoHelperServer GetTestRemotingCryptHelperServer()1001 {1002 PSRemotingCryptoHelperServer helper = new PSRemotingCryptoHelperServer();1003 helper.Session = new TestHelperSession();1004 1005 return helper;1006 }1007 1008 #endregion Internal Methods1009 }1010 1011 /// <summary>1012 /// Helper for exchanging keys and encrypting/decrypting1013 /// secure strings for serialization in remoting.1014 /// </summary>1015 internal class PSRemotingCryptoHelperClient : PSRemotingCryptoHelper1016 {1017 #region Private Members1018 1019 #endregion Private Members1020 1021 #region Constructors1022 1023 /// <summary>1024 /// Creates the encryption provider, but generates no key.1025 /// The key will be imported later.1026 /// </summary>1027 internal PSRemotingCryptoHelperClient()1028 {1029 _rsaCryptoProvider = PSRSACryptoServiceProvider.GetRSACryptoServiceProviderForClient();1030 }1031 1032 #endregion Constructors1033 1034 #region Protected Methods1035 1036 #endregion Protected Methods1037 1038 #region Internal Methods1039 1040 internal override string EncryptSecureString(SecureString secureString)1041 {1042 if (Session is ClientRemoteSession session && session.ServerProtocolVersion >= RemotingConstants.ProtocolVersion_2_4)1043 {1044 // For server v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing.1045 return ConvertSecureStringToBase64String(secureString);1046 }1047 1048 RunKeyExchangeIfRequired();1049 1050 return EncryptSecureStringCore(secureString);1051 }1052 1053 internal override SecureString DecryptSecureString(string encryptedString)1054 {1055 if (Session is ClientRemoteSession session && session.ServerProtocolVersion >= RemotingConstants.ProtocolVersion_2_4)1056 {1057 // For server v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing.1058 return ConvertBase64StringToSecureString(encryptedString);1059 }1060 1061 RunKeyExchangeIfRequired();1062 1063 return DecryptSecureStringCore(encryptedString);1064 }1065 1066 /// <summary>1067 /// Export the public key as a base64 encoded string.1068 /// </summary>1069 /// <param name="publicKeyAsString">on execution will contain1070 /// the public key as string</param>1071 /// <returns>True on success.</returns>1072 internal bool ExportLocalPublicKey(out string publicKeyAsString)1073 {1074 // generate keys - the method already takes of creating1075 // only when its not already created1076 1077 try1078 {1079 _rsaCryptoProvider.GenerateKeyPair();1080 }1081 catch (PSCryptoException)1082 {1083 throw;1084 1085 // the caller has to ensure that they1086 // complete the key exchange process1087 }1088 1089 try1090 {1091 publicKeyAsString = _rsaCryptoProvider.GetPublicKeyAsBase64EncodedString();1092 }1093 catch (PSCryptoException)1094 {1095 publicKeyAsString = string.Empty;1096 return false;1097 }1098 1099 return true;1100 }1101 1102 /// <summary>1103 /// </summary>1104 /// <param name="encryptedSessionKey"></param>1105 /// <returns></returns>1106 internal bool ImportEncryptedSessionKey(string encryptedSessionKey)1107 {1108 Dbg.Assert(!string.IsNullOrEmpty(encryptedSessionKey), "encrypted session key passed in cannot be null");1109 1110 try1111 {1112 _rsaCryptoProvider.ImportSessionKeyFromBase64EncodedString(encryptedSessionKey);1113 }1114 catch (PSCryptoException)1115 {1116 return false;1117 }1118 1119 return true;1120 }1121 1122 /// <summary>1123 /// Represents the session to be used for requesting public key.1124 /// </summary>1125 internal override RemoteSession Session { get; set; }1126 1127 /// <summary>1128 /// Gets a helper with a test session.1129 /// </summary>1130 /// <returns>Helper for testing.</returns>1131 /// <remarks>To be used only for testing</remarks>1132 internal static PSRemotingCryptoHelperClient GetTestRemotingCryptHelperClient()1133 {1134 PSRemotingCryptoHelperClient helper = new PSRemotingCryptoHelperClient();1135 helper.Session = new TestHelperSession();1136 1137 return helper;1138 }1139 1140 #endregion Internal Methods1141 }1142 1143 #region TestHelpers1144 1145 internal class TestHelperSession : RemoteSession1146 {1147 internal override void StartKeyExchange()1148 {1149 // intentionally left blank1150 }1151 1152 internal override RemotingDestination MySelf1153 {1154 get1155 {1156 return RemotingDestination.InvalidDestination;1157 }1158 }1159 1160 internal override void CompleteKeyExchange()1161 {1162 // intentionally left blank1163 }1164 }1165 #endregion TestHelpers1166}1167 