Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
SecureStringHelper.cs644 linesDownload Raw Back to security
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