MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Diagnostics;6using System.Globalization;7using System.IO;8using System.Management.Automation;9using System.Management.Automation.Internal;10using System.Runtime.InteropServices;11using System.Security;12using System.Security.Cryptography;13using System.Text;14 15namespace Microsoft.PowerShell16{17 /// <summary>18 /// Helper class for secure string related functionality.19 /// </summary>20 internal static class SecureStringHelper21 {22 // Some random hex characters to identify the beginning of a23 // V2-exported SecureString.24 internal static readonly string SecureStringExportHeader = "76492d1116743f0423413b16050a5345";25 26 /// <summary>27 /// Create a new SecureString based on the specified binary data.28 ///29 /// The binary data must be byte[] version of unicode char[],30 /// otherwise the results are unpredictable.31 /// </summary>32 /// <param name="data">Input data.</param>33 /// <returns>A SecureString .</returns>34 internal static SecureString New(byte[] data)35 {36 if ((data.Length % 2) != 0)37 {38 // If the data is not an even length, they supplied an invalid key39 string error = Serialization.InvalidKey;40 throw new PSArgumentException(error);41 }42 43 char ch;44 SecureString ss = new SecureString();45 46 //47 // each unicode char is 2 bytes.48 //49 int len = data.Length / 2;50 51 for (int i = 0; i < len; i++)52 {53 ch = (char)(data[2 * i + 1] * 256 + data[2 * i]);54 ss.AppendChar(ch);55 56 //57 // zero out the data slots as soon as we use them58 //59 data[2 * i] = 0;60 data[2 * i + 1] = 0;61 }62 63 return ss;64 }65 66 /// <summary>67 /// Get the contents of a SecureString as byte[]68 /// </summary>69 /// <param name="s">Input string.</param>70 /// <returns>Contents of s (char[]) converted to byte[].</returns>71 internal static byte[] GetData(SecureString s)72 {73 //74 // each unicode char is 2 bytes.75 //76 byte[] data = new byte[s.Length * 2];77 78 if (s.Length > 0)79 {80 IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(s);81 82 try83 {84 Marshal.Copy(ptr, data, 0, data.Length);85 }86 finally87 {88 Marshal.ZeroFreeCoTaskMemUnicode(ptr);89 }90 }91 92 return data;93 }94 95 /// <summary>96 /// Encode the specified byte[] as a unicode string.97 ///98 /// Currently we use simple hex encoding but this99 /// method can be changed to use a better encoding100 /// such as base64.101 /// </summary>102 /// <param name="data">Binary data to encode.</param>103 /// <returns>A string representing encoded data.</returns>104 internal static string ByteArrayToString(byte[] data)105 {106 StringBuilder sb = new StringBuilder();107 108 for (int i = 0; i < data.Length; i++)109 {110 sb.Append(data[i].ToString("x2", System.Globalization.CultureInfo.InvariantCulture));111 }112 113 return sb.ToString();114 }115 116 /// <summary>117 /// Convert a string obtained using ByteArrayToString()118 /// back to byte[] format.119 /// </summary>120 /// <param name="s">Encoded input string.</param>121 /// <returns>Bin data as byte[].</returns>122 internal static byte[] ByteArrayFromString(string s)123 {124 //125 // two hex chars per byte126 //127 int dataLen = s.Length / 2;128 byte[] data = new byte[dataLen];129 130 if (s.Length > 0)131 {132 for (int i = 0; i < dataLen; i++)133 {134 data[i] = byte.Parse(s.AsSpan(2 * i, 2),135 NumberStyles.AllowHexSpecifier,136 System.Globalization.CultureInfo.InvariantCulture);137 }138 }139 140 return data;141 }142 143 /// <summary>144 /// Return contents of the SecureString after encrypting145 /// using DPAPI and encoding the encrypted blob as a string.146 /// </summary>147 /// <param name="input">SecureString to protect.</param>148 /// <returns>A string (see summary) .</returns>149 internal static string Protect(SecureString input)150 {151 Utils.CheckSecureStringArg(input, "input");152 153 string output = string.Empty;154 byte[] data = null;155 byte[] protectedData = null;156 157 data = GetData(input);158#if UNIX159 // DPAPI doesn't exist on UNIX so we simply use the string as a byte-array160 protectedData = data;161#else162 protectedData = ProtectedData.Protect(data, null,163 DataProtectionScope.CurrentUser);164 for (int i = 0; i < data.Length; i++)165 {166 data[i] = 0;167 }168#endif169 170 output = ByteArrayToString(protectedData);171 172 return output;173 }174 175 /// <summary>176 /// Decrypts the specified string using DPAPI and return177 /// equivalent SecureString.178 ///179 /// The string must be obtained earlier by a call to Protect()180 /// </summary>181 /// <param name="input">Encrypted string.</param>182 /// <returns>SecureString .</returns>183 internal static SecureString Unprotect(string input)184 {185 Utils.CheckArgForNullOrEmpty(input, "input");186 if ((input.Length % 2) != 0)187 {188 throw PSTraceSource.NewArgumentException(nameof(input), Serialization.InvalidEncryptedString, input);189 }190 191 byte[] data = null;192 byte[] protectedData = null;193 SecureString s;194 195 protectedData = ByteArrayFromString(input);196 197#if UNIX198 // DPAPI isn't supported in UNIX, so we just translate the byte-array back to a string199 data = protectedData;200#else201 data = ProtectedData.Unprotect(protectedData, null,202 DataProtectionScope.CurrentUser);203 204#endif205 s = New(data);206 207 return s;208 }209 210 /// <summary>211 /// Return contents of the SecureString after encrypting212 /// using the specified key and encoding the encrypted blob as a string.213 /// </summary>214 /// <param name="input">Input string to encrypt.</param>215 /// <param name="key">Encryption key.</param>216 /// <returns>A string (see summary).</returns>217 internal static EncryptionResult Encrypt(SecureString input, SecureString key)218 {219 //220 // get clear text key from the SecureString key221 //222 byte[] keyBlob = GetData(key);223 224 //225 // encrypt the data226 //227 try228 {229 return Encrypt(input, keyBlob);230 }231 finally232 {233 Array.Clear(keyBlob);234 }235 }236 237 /// <summary>238 /// Return contents of the SecureString after encrypting239 /// using the specified key and encoding the encrypted blob as a string.240 /// </summary>241 /// <param name="input">Input string to encrypt.</param>242 /// <param name="key">Encryption key.</param>243 /// <returns>A string (see summary).</returns>244 internal static EncryptionResult Encrypt(SecureString input, byte[] key)245 {246 return Encrypt(input, key, null);247 }248 249 internal static EncryptionResult Encrypt(SecureString input, byte[] key, byte[] iv)250 {251 Utils.CheckSecureStringArg(input, "input");252 Utils.CheckKeyArg(key, "key");253 254 //255 // prepare the crypto stuff. Initialization Vector is256 // randomized by default.257 //258 using (Aes aes = Aes.Create())259 {260 iv ??= aes.IV;261 262 //263 // get clear text data from the input SecureString264 //265 byte[] data = GetData(input);266 try267 {268 using (ICryptoTransform encryptor = aes.CreateEncryptor(key, iv))269 using (var sourceStream = new MemoryStream(data))270 using (var encryptedStream = new MemoryStream())271 {272 //273 // encrypt it274 //275 using (var cryptoStream = new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write))276 {277 sourceStream.CopyTo(cryptoStream);278 }279 280 //281 // return encrypted data282 //283 byte[] encryptedData = encryptedStream.ToArray();284 return new EncryptionResult(ByteArrayToString(encryptedData), Convert.ToBase64String(iv));285 }286 }287 finally288 {289 Array.Clear(data, 0, data.Length);290 }291 }292 }293 294 /// <summary>295 /// Decrypts the specified string using the specified key296 /// and return equivalent SecureString.297 ///298 /// The string must be obtained earlier by a call to Encrypt()299 /// </summary>300 /// <param name="input">Encrypted string.</param>301 /// <param name="key">Encryption key.</param>302 /// <param name="IV">Encryption initialization vector. If this is set to null, the method uses internally computed strong random number as IV.</param>303 /// <returns>SecureString .</returns>304 internal static SecureString Decrypt(string input, SecureString key, byte[] IV)305 {306 //307 // get clear text key from the SecureString key308 //309 byte[] keyBlob = GetData(key);310 311 //312 // decrypt the data313 //314 try315 {316 return Decrypt(input, keyBlob, IV);317 }318 finally319 {320 Array.Clear(keyBlob);321 }322 }323 324 /// <summary>325 /// Decrypts the specified string using the specified key326 /// and return equivalent SecureString.327 ///328 /// The string must be obtained earlier by a call to Encrypt()329 /// </summary>330 /// <param name="input">Encrypted string.</param>331 /// <param name="key">Encryption key.</param>332 /// <param name="IV">Encryption initialization vector. If this is set to null, the method uses internally computed strong random number as IV.</param>333 /// <returns>SecureString .</returns>334 internal static SecureString Decrypt(string input, byte[] key, byte[] IV)335 {336 Utils.CheckArgForNullOrEmpty(input, "input");337 Utils.CheckKeyArg(key, "key");338 339 //340 // prepare the crypto stuff341 //342 using (var aes = Aes.Create())343 {344 using (ICryptoTransform decryptor = aes.CreateDecryptor(key, IV ?? aes.IV))345 using (var encryptedStream = new MemoryStream(ByteArrayFromString(input)))346 using (var targetStream = new MemoryStream())347 {348 //349 // decrypt the data and return as SecureString350 //351 using (var sourceStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))352 {353 sourceStream.CopyTo(targetStream);354 }355 356 byte[] decryptedData = targetStream.ToArray();357 try358 {359 return New(decryptedData);360 }361 finally362 {363 Array.Clear(decryptedData);364 }365 }366 }367 }368 369#nullable enable370 /// <summary>Creates a new <see cref="SecureString"/> from a <see cref="string"/>.</summary>371 /// <param name="plainTextString">Plain text string. Must not be null.</param>372 /// <returns>A new SecureString.</returns>373 internal static unsafe SecureString FromPlainTextString(string plainTextString)374 {375 Debug.Assert(plainTextString is not null);376 377 if (plainTextString.Length == 0)378 {379 return new SecureString();380 }381 382 fixed (char* charsPtr = plainTextString)383 {384 return new SecureString(charsPtr, plainTextString.Length);385 }386 }387#nullable restore388 }389 390 /// <summary>391 /// Helper class to return encryption results, and the IV used to392 /// do the encryption.393 /// </summary>394 internal class EncryptionResult395 {396 internal EncryptionResult(string encrypted, string IV)397 {398 EncryptedData = encrypted;399 this.IV = IV;400 }401 402 /// <summary>403 /// Gets the encrypted data.404 /// </summary>405 internal string EncryptedData { get; }406 407 /// <summary>408 /// Gets the IV used to encrypt the data.409 /// </summary>410 internal string IV { get; }411 }412 413#if !UNIX414 415 // The DPAPIs implemented in this section are temporary workaround.416 // CoreCLR team will bring 'ProtectedData' type to Project K eventually.417 418 #region DPAPI419 420 internal enum DataProtectionScope421 {422 CurrentUser = 0x00,423 LocalMachine = 0x01424 }425 426 internal static class ProtectedData427 {428 /// <summary>429 /// Protect.430 /// </summary>431 public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtectionScope scope)432 {433 ArgumentNullException.ThrowIfNull(userData);434 435 GCHandle pbDataIn = new GCHandle();436 GCHandle pOptionalEntropy = new GCHandle();437 CAPI.CRYPTOAPI_BLOB blob = new CAPI.CRYPTOAPI_BLOB();438 439 try440 {441 pbDataIn = GCHandle.Alloc(userData, GCHandleType.Pinned);442 CAPI.CRYPTOAPI_BLOB dataIn = new CAPI.CRYPTOAPI_BLOB();443 dataIn.cbData = (uint)userData.Length;444 dataIn.pbData = pbDataIn.AddrOfPinnedObject();445 CAPI.CRYPTOAPI_BLOB entropy = new CAPI.CRYPTOAPI_BLOB();446 if (optionalEntropy != null)447 {448 pOptionalEntropy = GCHandle.Alloc(optionalEntropy, GCHandleType.Pinned);449 entropy.cbData = (uint)optionalEntropy.Length;450 entropy.pbData = pOptionalEntropy.AddrOfPinnedObject();451 }452 453 uint dwFlags = CAPI.CRYPTPROTECT_UI_FORBIDDEN;454 if (scope == DataProtectionScope.LocalMachine)455 dwFlags |= CAPI.CRYPTPROTECT_LOCAL_MACHINE;456 unsafe457 {458 if (!CAPI.CryptProtectData(459 pDataIn: new IntPtr(&dataIn),460 szDataDescr: string.Empty,461 pOptionalEntropy: new IntPtr(&entropy),462 pvReserved: IntPtr.Zero,463 pPromptStruct: IntPtr.Zero,464 dwFlags: dwFlags,465 pDataBlob: new IntPtr(&blob)))466 {467 int lastWin32Error = Marshal.GetLastWin32Error();468 469 // One of the most common reasons that DPAPI operations fail is that the user470 // profile is not loaded (for instance in the case of impersonation or running in a471 // service. In those cases, throw an exception that provides more specific details472 // about what happened.473 if (CAPI.ErrorMayBeCausedByUnloadedProfile(lastWin32Error))474 {475 throw new CryptographicException("Cryptography_DpApi_ProfileMayNotBeLoaded");476 }477 else478 {479 throw new CryptographicException(lastWin32Error);480 }481 }482 }483 484 // In some cases, the API would fail due to OOM but simply return a null pointer.485 if (blob.pbData == IntPtr.Zero)486 {487 throw new OutOfMemoryException();488 }489 490 byte[] encryptedData = new byte[(int)blob.cbData];491 Marshal.Copy(blob.pbData, encryptedData, 0, encryptedData.Length);492 493 return encryptedData;494 }495 finally496 {497 if (pbDataIn.IsAllocated)498 {499 pbDataIn.Free();500 }501 if (pOptionalEntropy.IsAllocated)502 {503 pOptionalEntropy.Free();504 }505 if (blob.pbData != IntPtr.Zero)506 {507 CAPI.ZeroMemory(blob.pbData, blob.cbData);508 CAPI.LocalFree(blob.pbData);509 }510 }511 }512 513 /// <summary>514 /// Unprotect.515 /// </summary>516 public static byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope)517 {518 ArgumentNullException.ThrowIfNull(encryptedData);519 520 GCHandle pbDataIn = new GCHandle();521 GCHandle pOptionalEntropy = new GCHandle();522 CAPI.CRYPTOAPI_BLOB userData = new CAPI.CRYPTOAPI_BLOB();523 524 try525 {526 pbDataIn = GCHandle.Alloc(encryptedData, GCHandleType.Pinned);527 CAPI.CRYPTOAPI_BLOB dataIn = new CAPI.CRYPTOAPI_BLOB();528 dataIn.cbData = (uint)encryptedData.Length;529 dataIn.pbData = pbDataIn.AddrOfPinnedObject();530 CAPI.CRYPTOAPI_BLOB entropy = new CAPI.CRYPTOAPI_BLOB();531 if (optionalEntropy != null)532 {533 pOptionalEntropy = GCHandle.Alloc(optionalEntropy, GCHandleType.Pinned);534 entropy.cbData = (uint)optionalEntropy.Length;535 entropy.pbData = pOptionalEntropy.AddrOfPinnedObject();536 }537 538 uint dwFlags = CAPI.CRYPTPROTECT_UI_FORBIDDEN;539 if (scope == DataProtectionScope.LocalMachine)540 {541 dwFlags |= CAPI.CRYPTPROTECT_LOCAL_MACHINE;542 }543 544 unsafe545 {546 if (!CAPI.CryptUnprotectData(547 pDataIn: new IntPtr(&dataIn),548 ppszDataDescr: IntPtr.Zero,549 pOptionalEntropy: new IntPtr(&entropy),550 pvReserved: IntPtr.Zero,551 pPromptStruct: IntPtr.Zero,552 dwFlags: dwFlags,553 pDataBlob: new IntPtr(&userData)))554 {555 throw new CryptographicException(Marshal.GetLastWin32Error());556 }557 }558 559 // In some cases, the API would fail due to OOM but simply return a null pointer.560 if (userData.pbData == IntPtr.Zero)561 {562 throw new OutOfMemoryException();563 }564 565 byte[] data = new byte[(int)userData.cbData];566 Marshal.Copy(userData.pbData, data, 0, data.Length);567 568 return data;569 }570 finally571 {572 if (pbDataIn.IsAllocated)573 {574 pbDataIn.Free();575 }576 if (pOptionalEntropy.IsAllocated)577 {578 pOptionalEntropy.Free();579 }580 if (userData.pbData != IntPtr.Zero)581 {582 CAPI.ZeroMemory(userData.pbData, userData.cbData);583 CAPI.LocalFree(userData.pbData);584 }585 }586 }587 }588 589 internal static class CAPI590 {591 internal const uint CRYPTPROTECT_UI_FORBIDDEN = 0x1;592 internal const uint CRYPTPROTECT_LOCAL_MACHINE = 0x4;593 594 internal const int E_FILENOTFOUND = unchecked((int)0x80070002); // File not found595 internal const int ERROR_FILE_NOT_FOUND = 2; // File not found596 597 [StructLayout(LayoutKind.Sequential)]598 internal struct CRYPTOAPI_BLOB599 {600 internal uint cbData;601 internal IntPtr pbData;602 }603 604 internal static bool ErrorMayBeCausedByUnloadedProfile(int errorCode)605 {606 // CAPI returns a file not found error if the user profile is not yet loaded607 return errorCode == E_FILENOTFOUND ||608 errorCode == ERROR_FILE_NOT_FOUND;609 }610 611 [DllImport("CRYPT32.dll", CharSet = CharSet.Unicode, SetLastError = true)]612 [return: MarshalAs(UnmanagedType.Bool)]613 internal static extern bool CryptProtectData(614 [In] IntPtr pDataIn,615 [In] string szDataDescr,616 [In] IntPtr pOptionalEntropy,617 [In] IntPtr pvReserved,618 [In] IntPtr pPromptStruct,619 [In] uint dwFlags,620 [In, Out] IntPtr pDataBlob);621 622 [DllImport("CRYPT32.dll", CharSet = CharSet.Unicode, SetLastError = true)]623 [return: MarshalAs(UnmanagedType.Bool)]624 internal static extern bool CryptUnprotectData(625 [In] IntPtr pDataIn,626 [In] IntPtr ppszDataDescr,627 [In] IntPtr pOptionalEntropy,628 [In] IntPtr pvReserved,629 [In] IntPtr pPromptStruct,630 [In] uint dwFlags,631 [In, Out] IntPtr pDataBlob);632 633 [DllImport("ntdll.dll", EntryPoint = "RtlZeroMemory", SetLastError = true)]634 internal static extern void ZeroMemory(IntPtr handle, uint length);635 636 [DllImport(PinvokeDllNames.LocalFreeDllName, SetLastError = true)]637 internal static extern IntPtr LocalFree(IntPtr handle);638 }639 640 #endregion DPAPI641 642#endif643}644 