MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#pragma warning disable 1634, 16915#pragma warning disable 565236 7using System.Collections.Generic;8using System.Collections.ObjectModel;9using System.Diagnostics;10using System.Diagnostics.CodeAnalysis;11using System.IO;12using System.Globalization;13using System.Management.Automation.Configuration;14using System.Management.Automation.Internal;15using System.Management.Automation.Security;16using System.Runtime.InteropServices;17using System.Security.Cryptography;18using System.Security.Cryptography.X509Certificates;19using System.Text;20 21using Microsoft.PowerShell;22using Microsoft.PowerShell.Commands;23 24using DWORD = System.UInt32;25 26namespace Microsoft.PowerShell27{28 /// <summary>29 /// Defines the different Execution Policies supported by the30 /// PSAuthorizationManager class.31 /// </summary>32 public enum ExecutionPolicy33 {34 /// Unrestricted - No files must be signed. If a file originates from the35 /// internet, PowerShell provides a warning prompt to alert the user. To36 /// suppress this warning message, right-click on the file in File Explorer,37 /// select "Properties," and then "Unblock."38 Unrestricted = 0,39 40 /// RemoteSigned - Only .ps1 and .ps1xml files originating from the internet41 /// must be digitally signed. If remote, signed, and executed, PowerShell42 /// prompts to determine if files from the signing publisher should be43 /// run or not. This is the default setting.44 RemoteSigned = 1,45 46 /// AllSigned - All .ps1 and .ps1xml files must be digitally signed. If47 /// signed and executed, PowerShell prompts to determine if files from the48 /// signing publisher should be run or not.49 AllSigned = 2,50 51 /// Restricted - All .ps1 files are blocked. Ps1xml files must be digitally52 /// signed, and by a trusted publisher. If you haven't made a trust decision53 /// on the publisher yet, prompting is done as in AllSigned mode.54 Restricted = 3,55 56 /// Bypass - No files must be signed, and internet origin is not verified57 Bypass = 4,58 59 /// Undefined - Not specified at this scope60 Undefined = 5,61 62 /// <summary>63 /// Default - The most restrictive policy available.64 /// </summary>65 Default = Restricted66 }67 68 /// <summary>69 /// Defines the available configuration scopes for an execution70 /// policy. They are in the following priority, with successive71 /// elements overriding the items that precede them:72 /// LocalMachine -> CurrentUser -> Runspace.73 /// </summary>74 public enum ExecutionPolicyScope75 {76 /// Execution policy is retrieved from the77 /// PSExecutionPolicyPreference environment variable.78 Process = 0,79 80 /// Execution policy is retrieved from the HKEY_CURRENT_USER81 /// registry hive for the current ShellId.82 CurrentUser = 1,83 84 /// Execution policy is retrieved from the HKEY_LOCAL_MACHINE85 /// registry hive for the current ShellId.86 LocalMachine = 2,87 88 /// Execution policy is retrieved from the current user's89 /// group policy setting.90 UserPolicy = 3,91 92 /// Execution policy is retrieved from the machine-wide93 /// group policy setting.94 MachinePolicy = 495 }96}97 98namespace System.Management.Automation.Internal99{100 /// <summary>101 /// The SAFER policy associated with this file.102 /// </summary>103 internal enum SaferPolicy104 {105 /// Explicitly allowed through an Allow rule106 ExplicitlyAllowed = 0,107 108 /// Allowed because it has not been explicitly disallowed109 Allowed = 1,110 111 /// Disallowed by a rule or policy.112 Disallowed = 2113 }114 115 /// <summary>116 /// Security Support APIs.117 /// </summary>118 public static class SecuritySupport119 {120 #region execution policy121 122 internal static ExecutionPolicyScope[] ExecutionPolicyScopePreferences123 {124 get125 {126 return new ExecutionPolicyScope[] {127 ExecutionPolicyScope.MachinePolicy,128 ExecutionPolicyScope.UserPolicy,129 ExecutionPolicyScope.Process,130 ExecutionPolicyScope.CurrentUser,131 ExecutionPolicyScope.LocalMachine132 };133 }134 }135 136 internal static void SetExecutionPolicy(ExecutionPolicyScope scope, ExecutionPolicy policy, string shellId)137 {138#if UNIX139 throw new PlatformNotSupportedException();140#else141 string executionPolicy = "Restricted";142 143 switch (policy)144 {145 case ExecutionPolicy.Restricted:146 executionPolicy = "Restricted";147 break;148 case ExecutionPolicy.AllSigned:149 executionPolicy = "AllSigned";150 break;151 case ExecutionPolicy.RemoteSigned:152 executionPolicy = "RemoteSigned";153 break;154 case ExecutionPolicy.Unrestricted:155 executionPolicy = "Unrestricted";156 break;157 case ExecutionPolicy.Bypass:158 executionPolicy = "Bypass";159 break;160 }161 162 // Set the execution policy163 switch (scope)164 {165 case ExecutionPolicyScope.Process:166 167 if (policy == ExecutionPolicy.Undefined)168 executionPolicy = null;169 170 Environment.SetEnvironmentVariable("PSExecutionPolicyPreference", executionPolicy);171 break;172 173 case ExecutionPolicyScope.CurrentUser:174 175 // They want to remove it176 if (policy == ExecutionPolicy.Undefined)177 {178 PowerShellConfig.Instance.RemoveExecutionPolicy(ConfigScope.CurrentUser, shellId);179 }180 else181 {182 PowerShellConfig.Instance.SetExecutionPolicy(ConfigScope.CurrentUser, shellId, executionPolicy);183 }184 185 break;186 187 case ExecutionPolicyScope.LocalMachine:188 189 // They want to remove it190 if (policy == ExecutionPolicy.Undefined)191 {192 PowerShellConfig.Instance.RemoveExecutionPolicy(ConfigScope.AllUsers, shellId);193 }194 else195 {196 PowerShellConfig.Instance.SetExecutionPolicy(ConfigScope.AllUsers, shellId, executionPolicy);197 }198 199 break;200 }201#endif202 }203 204 internal static ExecutionPolicy GetExecutionPolicy(string shellId)205 {206 foreach (ExecutionPolicyScope scope in ExecutionPolicyScopePreferences)207 {208 ExecutionPolicy policy = GetExecutionPolicy(shellId, scope);209 if (policy != ExecutionPolicy.Undefined)210 return policy;211 }212 213 return ExecutionPolicy.Restricted;214 }215 216 private static bool? _hasGpScriptParent;217 218 /// <summary>219 /// A value indicating that the current process was launched by GPScript.exe220 /// Used to determine execution policy when group policies are in effect.221 /// </summary>222 /// <remarks>223 /// This is somewhat expensive to determine and does not change within the lifetime of the current process224 /// </remarks>225 private static bool HasGpScriptParent226 {227 get228 {229 if (!_hasGpScriptParent.HasValue)230 {231 _hasGpScriptParent = IsCurrentProcessLaunchedByGpScript();232 }233 234 return _hasGpScriptParent.Value;235 }236 }237 238 private static bool IsCurrentProcessLaunchedByGpScript()239 {240 Process currentProcess = Process.GetCurrentProcess();241 string gpScriptPath = IO.Path.Combine(242 Environment.GetFolderPath(Environment.SpecialFolder.System),243 "gpscript.exe");244 245 bool foundGpScriptParent = false;246 try247 {248 while (currentProcess != null)249 {250 if (string.Equals(gpScriptPath,251 currentProcess.MainModule.FileName, StringComparison.OrdinalIgnoreCase))252 {253 foundGpScriptParent = true;254 break;255 }256 else257 {258 currentProcess = PsUtils.GetParentProcess(currentProcess);259 }260 }261 }262 catch (System.ComponentModel.Win32Exception)263 {264 // If you attempt to retrieve the MainModule of a 64-bit process265 // from a WOW64 (32-bit) process, the Win32 API has a fatal266 // flaw that causes this to return the error:267 // "Only part of a ReadProcessMemory or WriteProcessMemory268 // request was completed."269 // In this case, we just catch the exception and eat it.270 // The implication is that logon / logoff scripts that somehow271 // launch the Wow64 version of PowerShell will be subject272 // to the execution policy deployed by Group Policy (where273 // our goal here is to not have the Group Policy execution policy274 // affect logon / logoff scripts.275 }276 277 return foundGpScriptParent;278 }279 280 internal static ExecutionPolicy GetExecutionPolicy(string shellId, ExecutionPolicyScope scope)281 {282#if UNIX283 return ExecutionPolicy.Unrestricted;284#else285 switch (scope)286 {287 case ExecutionPolicyScope.Process:288 {289 string policy = Environment.GetEnvironmentVariable("PSExecutionPolicyPreference");290 291 if (!string.IsNullOrEmpty(policy))292 return ParseExecutionPolicy(policy);293 else294 return ExecutionPolicy.Undefined;295 }296 297 case ExecutionPolicyScope.CurrentUser:298 case ExecutionPolicyScope.LocalMachine:299 {300 string policy = GetLocalPreferenceValue(shellId, scope);301 302 if (!string.IsNullOrEmpty(policy))303 return ParseExecutionPolicy(policy);304 else305 return ExecutionPolicy.Undefined;306 }307 308 // TODO: Group Policy is only supported on Full systems, but !LINUX && CORECLR309 // will run there as well, so I don't think we should remove it.310 case ExecutionPolicyScope.UserPolicy:311 case ExecutionPolicyScope.MachinePolicy:312 {313 string groupPolicyPreference = GetGroupPolicyValue(shellId, scope);314 315 // Be sure we aren't being called by Group Policy316 // itself. A group policy should never block a logon /317 // logoff script.318 if (string.IsNullOrEmpty(groupPolicyPreference) || HasGpScriptParent)319 {320 return ExecutionPolicy.Undefined;321 }322 323 return ParseExecutionPolicy(groupPolicyPreference);324 }325 }326 327 return ExecutionPolicy.Restricted;328#endif329 }330 331 internal static ExecutionPolicy ParseExecutionPolicy(string policy)332 {333 if (string.Equals(policy, "Bypass",334 StringComparison.OrdinalIgnoreCase))335 {336 return ExecutionPolicy.Bypass;337 }338 else if (string.Equals(policy, "Unrestricted",339 StringComparison.OrdinalIgnoreCase))340 {341 return ExecutionPolicy.Unrestricted;342 }343 else if (string.Equals(policy, "RemoteSigned",344 StringComparison.OrdinalIgnoreCase))345 {346 return ExecutionPolicy.RemoteSigned;347 }348 else if (string.Equals(policy, "AllSigned",349 StringComparison.OrdinalIgnoreCase))350 {351 return ExecutionPolicy.AllSigned;352 }353 else if (string.Equals(policy, "Restricted",354 StringComparison.OrdinalIgnoreCase))355 {356 return ExecutionPolicy.Restricted;357 }358 else359 {360 return ExecutionPolicy.Default;361 }362 }363 364 internal static string GetExecutionPolicy(ExecutionPolicy policy)365 {366 switch (policy)367 {368 case ExecutionPolicy.Bypass:369 return "Bypass";370 case ExecutionPolicy.Unrestricted:371 return "Unrestricted";372 case ExecutionPolicy.RemoteSigned:373 return "RemoteSigned";374 case ExecutionPolicy.AllSigned:375 return "AllSigned";376 case ExecutionPolicy.Restricted:377 return "Restricted";378 default:379 return "Restricted";380 }381 }382 383 /// <summary>384 /// Returns true if file has product binary signature.385 /// </summary>386 /// <param name="file">Name of file to check.</param>387 /// <returns>True when file has product binary signature.</returns>388 public static bool IsProductBinary(string file)389 {390 if (string.IsNullOrEmpty(file) || (!IO.File.Exists(file)))391 {392 return false;393 }394 395 // Check if it is in the product folder, if not, skip checking the catalog396 // and any other checks.397 var isUnderProductFolder = Utils.IsUnderProductFolder(file);398 if (!isUnderProductFolder)399 {400 return false;401 }402 403#if UNIX404 // There is no signature support on non-Windows platforms (yet), when405 // execution reaches here, we are sure the file is under product folder406 return true;407#else408 // Check the file signature409 Signature fileSignature = SignatureHelper.GetSignature(file, null);410 if ((fileSignature != null) && (fileSignature.IsOSBinary))411 {412 return true;413 }414 415 // WTGetSignatureInfo, via Microsoft.Security.Extensions, is used to verify catalog signature.416 // On Win7, catalog API is not available.417 // On OneCore SKUs like NanoServer/IoT, the API has a bug that makes it not able to find the418 // corresponding catalog file for a given product file, so it doesn't work properly.419 // In these cases, we just trust the 'isUnderProductFolder' check.420 if (Signature.CatalogApiAvailable.HasValue && !Signature.CatalogApiAvailable.Value)421 {422 // When execution reaches here, we are sure the file is under product folder423 return true;424 }425 426 return false;427#endif428 }429 430 /// <summary>431 /// Returns the value of the Execution Policy as retrieved432 /// from group policy.433 /// </summary>434 /// <returns>NULL if it is not defined at this level.</returns>435 private static string GetGroupPolicyValue(string shellId, ExecutionPolicyScope scope)436 {437 ConfigScope[] scopeKey = null;438 439 switch (scope)440 {441 case ExecutionPolicyScope.MachinePolicy:442 scopeKey = Utils.SystemWideOnlyConfig;443 break;444 445 case ExecutionPolicyScope.UserPolicy:446 scopeKey = Utils.CurrentUserOnlyConfig;447 break;448 }449 450 var scriptExecutionSetting = Utils.GetPolicySetting<ScriptExecution>(scopeKey);451 if (scriptExecutionSetting != null)452 {453 if (scriptExecutionSetting.EnableScripts == false)454 {455 // Script execution is explicitly disabled456 return "Restricted";457 }458 else if (scriptExecutionSetting.EnableScripts == true)459 {460 // Script execution is explicitly enabled461 return scriptExecutionSetting.ExecutionPolicy;462 }463 }464 465 return null;466 }467 468 /// <summary>469 /// Returns the value of the Execution Policy as retrieved470 /// from the local preference.471 /// </summary>472 /// <returns>NULL if it is not defined at this level.</returns>473 private static string GetLocalPreferenceValue(string shellId, ExecutionPolicyScope scope)474 {475 switch (scope)476 {477 // 1: Look up the current-user preference478 case ExecutionPolicyScope.CurrentUser:479 return PowerShellConfig.Instance.GetExecutionPolicy(ConfigScope.CurrentUser, shellId);480 481 // 2: Look up the system-wide preference482 case ExecutionPolicyScope.LocalMachine:483 return PowerShellConfig.Instance.GetExecutionPolicy(ConfigScope.AllUsers, shellId);484 }485 486 return null;487 }488 489 #endregion execution policy490 491 private static bool _saferIdentifyLevelApiSupported = true;492 493 /// <summary>494 /// Get the pass / fail result of calling the SAFER API.495 /// </summary>496 /// <param name="path">The path to the file in question.</param>497 /// <param name="handle">A file handle to the file in question, if available.</param>498 [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]499 internal static SaferPolicy GetSaferPolicy(string path, SafeHandle handle)500 {501 SaferPolicy status = SaferPolicy.Allowed;502 503 if (!_saferIdentifyLevelApiSupported)504 {505 return status;506 }507 508 SAFER_CODE_PROPERTIES codeProperties = new SAFER_CODE_PROPERTIES();509 IntPtr hAuthzLevel;510 511 // Prepare the code properties struct.512 codeProperties.cbSize = (uint)Marshal.SizeOf(typeof(SAFER_CODE_PROPERTIES));513 codeProperties.dwCheckFlags = (514 NativeConstants.SAFER_CRITERIA_IMAGEPATH |515 NativeConstants.SAFER_CRITERIA_IMAGEHASH |516 NativeConstants.SAFER_CRITERIA_AUTHENTICODE);517 codeProperties.ImagePath = path;518 519 if (handle != null)520 {521 codeProperties.hImageFileHandle = handle.DangerousGetHandle();522 }523 524 // turn off WinVerifyTrust UI525 codeProperties.dwWVTUIChoice = NativeConstants.WTD_UI_NONE;526 527 // Identify the level associated with the code528 if (NativeMethods.SaferIdentifyLevel(1, ref codeProperties, out hAuthzLevel, NativeConstants.SRP_POLICY_SCRIPT))529 {530 // We found an Authorization Level applicable to this application.531 IntPtr hRestrictedToken = IntPtr.Zero;532 try533 {534 if (!NativeMethods.SaferComputeTokenFromLevel(535 hAuthzLevel, // Safer Level536 IntPtr.Zero, // Test current process' token537 ref hRestrictedToken, // target token538 NativeConstants.SAFER_TOKEN_NULL_IF_EQUAL,539 IntPtr.Zero))540 {541 int lastError = Marshal.GetLastWin32Error();542 if ((lastError == NativeConstants.ERROR_ACCESS_DISABLED_BY_POLICY) ||543 (lastError == NativeConstants.ERROR_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY))544 {545 status = SaferPolicy.Disallowed;546 }547 else548 {549 throw new System.ComponentModel.Win32Exception();550 }551 }552 else553 {554 if (hRestrictedToken == IntPtr.Zero)555 {556 // This is not necessarily the "fully trusted" level,557 // it means that the thread token is complies with the requested level558 status = SaferPolicy.Allowed;559 }560 else561 {562 status = SaferPolicy.Disallowed;563 NativeMethods.CloseHandle(hRestrictedToken);564 }565 }566 }567 finally568 {569 NativeMethods.SaferCloseLevel(hAuthzLevel);570 }571 }572 else573 {574 int lastError = Marshal.GetLastWin32Error();575 if (lastError == NativeConstants.FUNCTION_NOT_SUPPORTED)576 {577 _saferIdentifyLevelApiSupported = false;578 }579 else580 {581 throw new System.ComponentModel.Win32Exception(lastError);582 }583 }584 585 return status;586 }587 588 /// <summary>589 /// Throw if file does not exist.590 /// </summary>591 /// <param name="filePath">Path to file.</param>592 /// <returns>Does not return a value.</returns>593 internal static void CheckIfFileExists(string filePath)594 {595 if (!File.Exists(filePath))596 {597 throw new FileNotFoundException(filePath);598 }599 }600 601 /// <summary>602 /// Check to see if the specified cert is suitable to be603 /// used as a code signing cert.604 /// </summary>605 /// <param name="c">Certificate object.</param>606 /// <returns>True on success, false otherwise.</returns>607 internal static bool CertIsGoodForSigning(X509Certificate2 c)608 {609 if (!c.HasPrivateKey)610 {611 return false;612 }613 614 return CertHasOid(c, CertificateFilterInfo.CodeSigningOid);615 }616 617 /// <summary>618 /// Check to see if the specified cert is suitable to be619 /// used as an encryption cert for PKI encryption. Note620 /// that this cert doesn't require the private key.621 /// </summary>622 /// <param name="c">Certificate object.</param>623 /// <returns>True on success, false otherwise.</returns>624 internal static bool CertIsGoodForEncryption(X509Certificate2 c)625 {626 return (627 CertHasOid(c, CertificateFilterInfo.DocumentEncryptionOid) &&628 (CertHasKeyUsage(c, X509KeyUsageFlags.DataEncipherment) ||629 CertHasKeyUsage(c, X509KeyUsageFlags.KeyEncipherment)));630 }631 632 /// <summary>633 /// Check to see if the specified cert is expiring by the time.634 /// </summary>635 /// <param name="c">Certificate object.</param>636 /// <param name="expiring">Certificate expire time.</param>637 /// <returns>True on success, false otherwise.</returns>638 internal static bool CertExpiresByTime(X509Certificate2 c, DateTime expiring)639 {640 return c.NotAfter < expiring;641 }642 643 private static bool CertHasOid(X509Certificate2 c, string oid)644 {645 foreach (var extension in c.Extensions)646 {647 if (extension is X509EnhancedKeyUsageExtension ext)648 {649 foreach (Oid ekuOid in ext.EnhancedKeyUsages)650 {651 if (ekuOid.Value == oid)652 {653 return true;654 }655 }656 break;657 }658 }659 return false;660 }661 662 private static bool CertHasKeyUsage(X509Certificate2 c, X509KeyUsageFlags keyUsage)663 {664 foreach (X509Extension extension in c.Extensions)665 {666 if (extension is X509KeyUsageExtension keyUsageExtension)667 {668 if ((keyUsageExtension.KeyUsages & keyUsage) == keyUsage)669 {670 return true;671 }672 break;673 }674 }675 return false;676 }677 678 /// <summary>679 /// Get the EKUs of a cert.680 /// </summary>681 /// <param name="cert">Certificate object.</param>682 /// <returns>A collection of cert eku strings.</returns>683 internal static Collection<string> GetCertEKU(X509Certificate2 cert)684 {685 Collection<string> ekus = new Collection<string>();686 IntPtr pCert = cert.Handle;687 int structSize = 0;688 IntPtr dummy = IntPtr.Zero;689 690 if (Security.NativeMethods.CertGetEnhancedKeyUsage(pCert, 0, dummy,691 out structSize))692 {693 if (structSize > 0)694 {695 IntPtr ekuBuffer = Marshal.AllocHGlobal(structSize);696 697 try698 {699 if (Security.NativeMethods.CertGetEnhancedKeyUsage(pCert, 0,700 ekuBuffer,701 out structSize))702 {703 Security.NativeMethods.CERT_ENHKEY_USAGE ekuStruct =704 Marshal.PtrToStructure<Security.NativeMethods.CERT_ENHKEY_USAGE>(ekuBuffer);705 IntPtr ep = ekuStruct.rgpszUsageIdentifier;706 IntPtr ekuptr;707 708 for (int i = 0; i < ekuStruct.cUsageIdentifier; i++)709 {710 ekuptr = Marshal.ReadIntPtr(ep, i * Marshal.SizeOf(ep));711 string eku = Marshal.PtrToStringAnsi(ekuptr);712 ekus.Add(eku);713 }714 }715 else716 {717 throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());718 }719 }720 finally721 {722 Marshal.FreeHGlobal(ekuBuffer);723 }724 }725 }726 else727 {728 throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());729 }730 731 return ekus;732 }733 734 /// <summary>735 /// Convert an int to a DWORD.736 /// </summary>737 /// <param name="n">Signed int number.</param>738 /// <returns>DWORD.</returns>739 internal static DWORD GetDWORDFromInt(int n)740 {741 UInt32 result = BitConverter.ToUInt32(BitConverter.GetBytes(n), 0);742 return (DWORD)result;743 }744 745 /// <summary>746 /// Convert a DWORD to int.747 /// </summary>748 /// <param name="n">Number.</param>749 /// <returns>Int.</returns>750 internal static int GetIntFromDWORD(DWORD n)751 {752 Int64 n64 = n - 0x100000000L;753 return (int)n64;754 }755 }756 757 /// <summary>758 /// Information used for filtering a set of certs.759 /// </summary>760 internal sealed class CertificateFilterInfo761 {762 internal CertificateFilterInfo()763 {764 }765 766 /// <summary>767 /// Gets or sets purpose of a certificate.768 /// </summary>769 internal CertificatePurpose Purpose770 {771 get;772 set;773 } = CertificatePurpose.NotSpecified;774 775 /// <summary>776 /// Gets or sets SSL Server Authentication.777 /// </summary>778 internal bool SSLServerAuthentication779 {780 get;781 782 set;783 }784 785 /// <summary>786 /// Gets or sets DNS name of a certificate.787 /// </summary>788 internal WildcardPattern DnsName789 {790 get;791 set;792 }793 794 /// <summary>795 /// Gets or sets EKU OID list of a certificate.796 /// </summary>797 internal List<WildcardPattern> Eku798 {799 get;800 set;801 }802 803 /// <summary>804 /// Gets or sets validity time for a certificate.805 /// </summary>806 internal DateTime Expiring807 {808 get;809 set;810 } = DateTime.MinValue;811 812 internal const string CodeSigningOid = "1.3.6.1.5.5.7.3.3";813 internal const string OID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";814 815 // The OID arc 1.3.6.1.4.1.311.80 is assigned to PowerShell. If we need816 // new OIDs, we can assign them under this branch.817 internal const string DocumentEncryptionOid = "1.3.6.1.4.1.311.80.1";818 internal const string SubjectAlternativeNameOid = "2.5.29.17";819 }820}821 822namespace Microsoft.PowerShell.Commands823{824 /// <summary>825 /// Defines the valid purposes by which826 /// we can filter certificates.827 /// </summary>828 internal enum CertificatePurpose829 {830 /// <summary>831 /// Certificates where a purpose has not been specified.832 /// </summary>833 NotSpecified = 0,834 835 /// <summary>836 /// Certificates that can be used to sign837 /// code and scripts.838 /// </summary>839 CodeSigning = 0x1,840 841 /// <summary>842 /// Certificates that can be used to encrypt843 /// data.844 /// </summary>845 DocumentEncryption = 0x2,846 847 /// <summary>848 /// Certificates that can be used for any849 /// purpose.850 /// </summary>851 All = 0xffff852 }853}854 855namespace System.Management.Automation856{857 using System.Management.Automation.Tracing;858 using System.Security.Cryptography.Pkcs;859 860 /// <summary>861 /// Utility class for CMS (Cryptographic Message Syntax) related operations.862 /// </summary>863 internal static class CmsUtils864 {865 internal static string Encrypt(byte[] contentBytes, CmsMessageRecipient[] recipients, SessionState sessionState, out ErrorRecord error)866 {867 error = null;868 869 if ((contentBytes == null) || (contentBytes.Length == 0))870 {871 return string.Empty;872 }873 874 // After review with the crypto board, NIST_AES256_CBC is more appropriate875 // than .NET's default 3DES. Also, when specified, uses szOID_RSAES_OAEP for key876 // encryption to prevent padding attacks.877 const string szOID_NIST_AES256_CBC = "2.16.840.1.101.3.4.1.42";878 879 ContentInfo content = new ContentInfo(contentBytes);880 EnvelopedCms cms = new EnvelopedCms(content,881 new AlgorithmIdentifier(882 Oid.FromOidValue(szOID_NIST_AES256_CBC, OidGroup.EncryptionAlgorithm)));883 884 CmsRecipientCollection recipientCollection = new CmsRecipientCollection();885 foreach (CmsMessageRecipient recipient in recipients)886 {887 // Resolve the recipient, if it hasn't been done yet.888 if ((recipient.Certificates != null) && (recipient.Certificates.Count == 0))889 {890 recipient.Resolve(sessionState, ResolutionPurpose.Encryption, out error);891 }892 893 if (error != null)894 {895 return null;896 }897 898 foreach (X509Certificate2 certificate in recipient.Certificates)899 {900 recipientCollection.Add(new CmsRecipient(certificate));901 }902 }903 904 cms.Encrypt(recipientCollection);905 906 byte[] encodedBytes = cms.Encode();907 string encodedContent = CmsUtils.GetAsciiArmor(encodedBytes);908 return encodedContent;909 }910 911 internal static readonly string BEGIN_CMS_SIGIL = "-----BEGIN CMS-----";912 internal static readonly string END_CMS_SIGIL = "-----END CMS-----";913 914 internal static readonly string BEGIN_CERTIFICATE_SIGIL = "-----BEGIN CERTIFICATE-----";915 internal static readonly string END_CERTIFICATE_SIGIL = "-----END CERTIFICATE-----";916 917 /// <summary>918 /// Adds Ascii armour to a byte stream in Base64 format.919 /// </summary>920 /// <param name="bytes">The bytes to encode.</param>921 internal static string GetAsciiArmor(byte[] bytes)922 {923 StringBuilder output = new StringBuilder();924 output.AppendLine(BEGIN_CMS_SIGIL);925 926 string encodedString = Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks);927 output.AppendLine(encodedString);928 output.Append(END_CMS_SIGIL);929 930 return output.ToString();931 }932 933 /// <summary>934 /// Removes Ascii armour from a byte stream.935 /// </summary>936 /// <param name="actualContent">The Ascii armored content.</param>937 /// <param name="beginMarker">The marker of the start of the Base64 content.</param>938 /// <param name="endMarker">The marker of the end of the Base64 content.</param>939 /// <param name="startIndex">The beginning of where the Ascii armor was detected.</param>940 /// <param name="endIndex">The end of where the Ascii armor was detected.</param>941 internal static byte[] RemoveAsciiArmor(string actualContent, string beginMarker, string endMarker, out int startIndex, out int endIndex)942 {943 byte[] messageBytes = null;944 startIndex = -1;945 endIndex = -1;946 947 startIndex = actualContent.IndexOf(beginMarker, StringComparison.OrdinalIgnoreCase);948 if (startIndex < 0)949 {950 return null;951 }952 953 endIndex = actualContent.IndexOf(endMarker, startIndex, StringComparison.OrdinalIgnoreCase) +954 endMarker.Length;955 if (endIndex < endMarker.Length)956 {957 return null;958 }959 960 int startContent = startIndex + beginMarker.Length;961 int endContent = endIndex - endMarker.Length;962 string encodedContent = actualContent.Substring(startContent, endContent - startContent);963 encodedContent = System.Text.RegularExpressions.Regex.Replace(encodedContent, "\\s", string.Empty);964 messageBytes = Convert.FromBase64String(encodedContent);965 966 return messageBytes;967 }968 }969 970 /// <summary>971 /// Represents a message recipient for the Cms cmdlets.972 /// </summary>973 public class CmsMessageRecipient974 {975 /// <summary>976 /// Creates an instance of the CmsMessageRecipient class.977 /// </summary>978 internal CmsMessageRecipient() { }979 980 /// <summary>981 /// Creates an instance of the CmsMessageRecipient class.982 /// </summary>983 /// <param name="identifier">984 /// The identifier of the CmsMessageRecipient.985 /// Can be either:986 /// - The path to a file containing the certificate987 /// - The path to a directory containing the certificate988 /// - The thumbprint of the certificate, used to find the certificate in the certificate store989 /// - The Subject name of the recipient, used to find the certificate in the certificate store990 /// </param>991 public CmsMessageRecipient(string identifier)992 {993 _identifier = identifier;994 this.Certificates = new X509Certificate2Collection();995 }996 997 private readonly string _identifier;998 999 /// <summary>1000 /// Creates an instance of the CmsMessageRecipient class.1001 /// </summary>1002 /// <param name="certificate">The certificate to use.</param>1003 public CmsMessageRecipient(X509Certificate2 certificate)1004 {1005 _pendingCertificate = certificate;1006 this.Certificates = new X509Certificate2Collection();1007 }1008 1009 private readonly X509Certificate2 _pendingCertificate;1010 1011 /// <summary>1012 /// Gets the certificate associated with this recipient.1013 /// </summary>1014 public X509Certificate2Collection Certificates1015 {1016 get;1017 internal set;1018 }1019 1020 /// <summary>1021 /// Resolves the provided identifier into a collection of certificates.1022 /// </summary>1023 /// <param name="sessionState">A reference to an instance of Powershell's SessionState class.</param>1024 /// <param name="purpose">The purpose for which this identifier is being resolved (Encryption / Decryption.</param>1025 /// <param name="error">The error generated (if any) for this resolution.</param>1026 public void Resolve(SessionState sessionState, ResolutionPurpose purpose, out ErrorRecord error)1027 {1028 error = null;1029 1030 // Process the certificate if that was supplied exactly1031 if (_pendingCertificate != null)1032 {1033 ProcessResolvedCertificates(1034 purpose,1035 new X509Certificate2Collection(_pendingCertificate),1036 out error);1037 if ((error != null) || (Certificates.Count != 0))1038 {1039 return;1040 }1041 }1042 1043 if (_identifier != null)1044 {1045 // First try to resolve assuming that the cert was Base64 encoded.1046 ResolveFromBase64Encoding(purpose, out error);1047 if ((error != null) || (Certificates.Count != 0))1048 {1049 return;1050 }1051 1052 // Then try to resolve by path.1053 ResolveFromPath(sessionState, purpose, out error);1054 if ((error != null) || (Certificates.Count != 0))1055 {1056 return;1057 }1058 1059 // Then by cert store1060 ResolveFromStoreById(purpose, out error);1061 if ((error != null) || (Certificates.Count != 0))1062 {1063 return;1064 }1065 }1066 1067 // Generate an error if no cert was found (and this is an encryption attempt).1068 // If it is only decryption, then the system will always look in the 'My' store anyways, so1069 // don't generate an error if they used wildcards. If they did not use wildcards,1070 // then generate an error because they were expecting something specific.1071 if ((purpose == ResolutionPurpose.Encryption) ||1072 (!WildcardPattern.ContainsWildcardCharacters(_identifier)))1073 {1074 error = new ErrorRecord(1075 new ArgumentException(1076 string.Format(CultureInfo.InvariantCulture,1077 SecuritySupportStrings.NoCertificateFound, _identifier)),1078 "NoCertificateFound", ErrorCategory.ObjectNotFound, _identifier);1079 }1080 1081 return;1082 }1083 1084 private void ResolveFromBase64Encoding(ResolutionPurpose purpose, out ErrorRecord error)1085 {1086 error = null;1087 int startIndex, endIndex;1088 byte[] messageBytes = null;1089 try1090 {1091 messageBytes = CmsUtils.RemoveAsciiArmor(_identifier, CmsUtils.BEGIN_CERTIFICATE_SIGIL, CmsUtils.END_CERTIFICATE_SIGIL, out startIndex, out endIndex);1092 }1093 catch (FormatException)1094 {1095 // Not Base-64 encoded1096 return;1097 }1098 1099 // Didn't have the sigil1100 if (messageBytes == null)1101 {1102 return;1103 }1104 1105 var certificatesToProcess = new X509Certificate2Collection();1106 try1107 {1108 #pragma warning disable SYSLIB00571109 X509Certificate2 newCertificate = new X509Certificate2(messageBytes);1110 #pragma warning restore SYSLIB00571111 1112 certificatesToProcess.Add(newCertificate);1113 }1114 catch (Exception)1115 {1116 // User call-out, catch-all OK1117 1118 // Wasn't certificate data1119 return;1120 }1121 1122 // Now validate the certificate1123 ProcessResolvedCertificates(purpose, certificatesToProcess, out error);1124 }1125 1126 private void ResolveFromPath(SessionState sessionState, ResolutionPurpose purpose, out ErrorRecord error)1127 {1128 error = null;1129 ProviderInfo pathProvider = null;1130 Collection<string> resolvedPaths = null;1131 1132 try1133 {1134 resolvedPaths = sessionState.Path.GetResolvedProviderPathFromPSPath(_identifier, out pathProvider);1135 }1136 catch (SessionStateException)1137 {1138 // If we got an ItemNotFound / etc., then this didn't represent a valid path.1139 }1140 1141 // If we got a resolved path, try to load certs from that path.1142 if ((resolvedPaths != null) && (resolvedPaths.Count != 0))1143 {1144 // Ensure the path is from the file system provider1145 if (!string.Equals(pathProvider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase))1146 {1147 error = new ErrorRecord(1148 new ArgumentException(1149 string.Format(CultureInfo.InvariantCulture,1150 SecuritySupportStrings.CertificatePathMustBeFileSystemPath, _identifier)),1151 "CertificatePathMustBeFileSystemPath", ErrorCategory.ObjectNotFound, pathProvider);1152 return;1153 }1154 1155 // If this is a directory, add all certificates in it. This will be the primary1156 // scenario for decryption via Group Protected PFX files1157 // (http://social.technet.microsoft.com/wiki/contents/articles/13922.certificate-pfx-export-and-import-using-ad-ds-account-protection.aspx)1158 List<string> pathsToAdd = new List<string>();1159 List<string> pathsToRemove = new List<string>();1160 foreach (string resolvedPath in resolvedPaths)1161 {1162 if (System.IO.Directory.Exists(resolvedPath))1163 {1164 // It would be nice to limit this to *.pfx, *.cer, etc., but1165 // the crypto APIs support extracting certificates from arbitrary file types.1166 pathsToAdd.AddRange(System.IO.Directory.GetFiles(resolvedPath));1167 pathsToRemove.Add(resolvedPath);1168 }1169 }1170 1171 // Update resolved paths1172 foreach (string path in pathsToAdd)1173 {1174 resolvedPaths.Add(path);1175 }1176 1177 foreach (string path in pathsToRemove)1178 {1179 resolvedPaths.Remove(path);1180 }1181 1182 var certificatesToProcess = new X509Certificate2Collection();1183 foreach (string path in resolvedPaths)1184 {1185 X509Certificate2 certificate = null;1186 1187 try1188 {1189 #pragma warning disable SYSLIB00571190 certificate = new X509Certificate2(path);1191 #pragma warning restore SYSLIB00571192 }1193 catch (Exception)1194 {1195 // User call-out, catch-all OK1196 continue;1197 }1198 1199 certificatesToProcess.Add(certificate);1200 }