MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#if !UNIX5 6using System;7using System.Collections;8using System.Collections.Generic;9using System.Collections.ObjectModel;10using System.Diagnostics;11using System.Diagnostics.CodeAnalysis;12using System.Globalization;13using System.IO;14using System.Management.Automation;15using System.Management.Automation.Host;16using System.Management.Automation.Internal;17using System.Management.Automation.Provider;18using System.Runtime.InteropServices;19using System.Security;20using System.Security.Cryptography;21using System.Security.Cryptography.X509Certificates;22using System.Text.RegularExpressions;23using System.Xml;24using System.Xml.XPath;25 26using Dbg = System.Management.Automation;27using DWORD = System.UInt32;28using Runspaces = System.Management.Automation.Runspaces;29using SMASecurity = System.Management.Automation.Security;30 31namespace Microsoft.PowerShell.Commands32{33 /// <summary>34 /// Defines the Certificate Provider dynamic parameters.35 /// We only support one dynamic parameter for Win 7 and earlier:36 /// CodeSigningCert37 /// If provided, we only return certificates valid for signing code or38 /// scripts.39 /// </summary>40 internal sealed class CertificateProviderDynamicParameters41 {42 /// <summary>43 /// Gets or sets a switch that controls whether we only return44 /// code signing certs.45 /// </summary>46 [Parameter]47 public SwitchParameter CodeSigningCert48 {49 get { return _codeSigningCert; }50 51 set { _codeSigningCert = value; }52 }53 54 private SwitchParameter _codeSigningCert = new();55 56 /// <summary>57 /// Gets or sets a filter that controls whether we only return58 /// data encipherment certs.59 /// </summary>60 [Parameter]61 public SwitchParameter DocumentEncryptionCert62 {63 get;64 set;65 }66 67 /// <summary>68 /// Gets or sets a filter that controls whether we only return69 /// server authentication certs.70 /// </summary>71 [Parameter]72 public SwitchParameter SSLServerAuthentication73 {74 get;75 set;76 }77 78 /// <summary>79 /// Gets or sets a filter by DNSName.80 /// Expected content is a single DNS Name that may start and/or end81 /// with '*': "contoso.com" or "*toso.c*".82 /// All WildcardPattern class features supported.83 /// </summary>84 [Parameter]85 public string DnsName86 {87 get;88 set;89 }90 91 /// <summary>92 /// Gets or sets a filter by EKU.93 /// Expected content is one or more OID strings:94 /// "1.3.6.1.5.5.7.3.1", "*Server*", etc.95 /// For a cert to match, it must be valid for all listed OIDs.96 /// All WildcardPattern class features supported.97 /// </summary>98 [Parameter]99 public string[] Eku100 {101 get;102 set;103 }104 105 /// <summary>106 /// Gets or sets a filter by the number of valid days.107 /// Expected content is a non-negative integer.108 /// "0" matches all certs that have already expired.109 /// "1" matches all certs that are currently valid and will expire110 /// by next day (local time).111 /// </summary>112 [Parameter]113 [ValidateRange(ValidateRangeKind.NonNegative)]114 public int ExpiringInDays115 {116 get;117 set;118 } = -1;119 }120 121 /// <summary>122 /// Defines the type of DNS string123 /// The structure contains punycode name and unicode name.124 /// </summary>125 [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]126 public readonly struct DnsNameRepresentation127 {128 /// <summary>129 /// Punycode version of DNS name.130 /// </summary>131 private readonly string _punycodeName;132 133 /// <summary>134 /// Unicode version of DNS name.135 /// </summary>136 private readonly string _unicodeName;137 138 /// <summary>139 /// Ambiguous constructor of a DnsNameRepresentation.140 /// </summary>141 public DnsNameRepresentation(string inputDnsName)142 {143 _punycodeName = inputDnsName;144 _unicodeName = inputDnsName;145 }146 147 /// <summary>148 /// Specific constructor of a DnsNameRepresentation.149 /// </summary>150 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Punycode")]151 public DnsNameRepresentation(152 string inputPunycodeName,153 string inputUnicodeName)154 {155 _punycodeName = inputPunycodeName;156 _unicodeName = inputUnicodeName;157 }158 159 /// <summary>160 /// Value comparison.161 /// </summary>162 public bool Equals(DnsNameRepresentation dnsName)163 {164 bool match = false;165 166 if (_unicodeName != null && dnsName._unicodeName != null)167 {168 if (string.Equals(169 _unicodeName,170 dnsName._unicodeName,171 StringComparison.OrdinalIgnoreCase))172 {173 match = true;174 }175 }176 else if (_unicodeName == null && dnsName._unicodeName == null)177 {178 match = true;179 }180 181 return match;182 }183 184 /// <summary>185 /// Get property of Punycode.186 /// </summary>187 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Punycode")]188 public string Punycode189 {190 get191 {192 return _punycodeName;193 }194 }195 196 /// <summary>197 /// Get property of Unicode.198 /// </summary>199 public string Unicode200 {201 get202 {203 return _unicodeName;204 }205 }206 207 /// <summary>208 /// Get display string.209 /// </summary>210 public override string ToString()211 {212 // Use case sensitive comparison here.213 // We don't ever expect to see the punycode and unicode strings214 // to differ only by upper/lower case. If they do, that's really215 // a code bug, and the effect is to just display both strings.216 217 return string.Equals(_punycodeName, _unicodeName, StringComparison.Ordinal)218 ? _punycodeName219 : _unicodeName + " (" + _punycodeName + ")";220 }221 }222 223 /// <summary>224 /// Defines the Certificate Provider remove-item dynamic parameters.225 ///226 /// Currently, we only support one dynamic parameter: DeleteKey227 /// If provided, we will delete the private key when we remove a certificate.228 /// </summary>229 internal sealed class ProviderRemoveItemDynamicParameters230 {231 /// <summary>232 /// Switch that controls whether we should delete private key233 /// when remove a certificate.234 /// </summary>235 [Parameter]236 public SwitchParameter DeleteKey237 {238 get239 {240 {241 return _deleteKey;242 }243 }244 245 set246 {247 {248 _deleteKey = value;249 }250 }251 }252 253 private SwitchParameter _deleteKey = new();254 }255 256 /// <summary>257 /// Defines the safe handle class for native cert store handles,258 /// HCERTSTORE.259 /// </summary>260 internal sealed class CertificateStoreHandle : SafeHandle261 {262 public CertificateStoreHandle() : base(IntPtr.Zero, true)263 {264 return;265 }266 267 public override bool IsInvalid268 {269 get { return handle == IntPtr.Zero; }270 }271 272 protected override bool ReleaseHandle()273 {274 bool fResult = false;275 276 if (handle != IntPtr.Zero)277 {278 fResult = SMASecurity.NativeMethods.CertCloseStore(handle, 0);279 handle = IntPtr.Zero;280 }281 282 return fResult;283 }284 285 public IntPtr Handle286 {287 get { return handle; }288 289 set { handle = value; }290 }291 }292 293 /// <summary>294 /// Defines the Certificate Provider store handle class.295 /// </summary>296 internal sealed class X509NativeStore297 {298 // #region tracer299 300 /// <summary>301 /// Initializes a new instance of the X509NativeStore class.302 /// </summary>303 public X509NativeStore(X509StoreLocation StoreLocation, string StoreName)304 {305 _storeLocation = StoreLocation;306 _storeName = StoreName;307 }308 309 public void Open(bool includeArchivedCerts)310 {311 if (_storeHandle != null && _archivedCerts != includeArchivedCerts)312 {313 _storeHandle = null; // release the old handle314 }315 316 if (_storeHandle == null)317 {318 _valid = false;319 _open = false;320 321 SMASecurity.NativeMethods.CertOpenStoreFlags StoreFlags =322 SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_SHARE_STORE_FLAG |323 SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_SHARE_CONTEXT_FLAG |324 SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_OPEN_EXISTING_FLAG |325 SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_MAXIMUM_ALLOWED_FLAG;326 327 if (includeArchivedCerts)328 {329 StoreFlags |= SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_ENUM_ARCHIVED_FLAG;330 }331 332 switch (_storeLocation.Location)333 {334 case StoreLocation.LocalMachine:335 StoreFlags |= SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE;336 break;337 338 case StoreLocation.CurrentUser:339 StoreFlags |= SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_CURRENT_USER;340 break;341 342 default:343 // ThrowItemNotFound(storeLocation.ToString(), CertificateProviderItem.StoreLocation);344 break;345 }346 347 IntPtr hCertStore = SMASecurity.NativeMethods.CertOpenStore(348 SMASecurity.NativeMethods.CertOpenStoreProvider.CERT_STORE_PROV_SYSTEM,349 SMASecurity.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING,350 IntPtr.Zero, // hCryptProv351 StoreFlags,352 _storeName);353 if (hCertStore == IntPtr.Zero)354 {355 throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());356 }357 358 _storeHandle = new CertificateStoreHandle();359 _storeHandle.Handle = hCertStore;360 361 // we only do CertControlStore for stores other than UserDS362 if (!string.Equals(363 _storeName,364 "UserDS",365 StringComparison.OrdinalIgnoreCase))366 {367 if (!SMASecurity.NativeMethods.CertControlStore(368 _storeHandle.Handle,369 0,370 SMASecurity.NativeMethods.CertControlStoreType.CERT_STORE_CTRL_AUTO_RESYNC,371 IntPtr.Zero))372 {373 _storeHandle = null;374 throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());375 }376 }377 378 _valid = true;379 _open = true;380 _archivedCerts = includeArchivedCerts;381 }382 }383 384 public IntPtr GetFirstCert()385 {386 return GetNextCert(IntPtr.Zero);387 }388 389 public IntPtr GetNextCert(IntPtr certContext)390 {391 if (!_open)392 {393 throw Marshal.GetExceptionForHR(394 SMASecurity.NativeMethods.CRYPT_E_NOT_FOUND);395 }396 397 if (Valid)398 {399 certContext = SMASecurity.NativeMethods.CertEnumCertificatesInStore(400 _storeHandle.Handle,401 certContext);402 }403 else404 {405 certContext = IntPtr.Zero;406 }407 408 return certContext;409 }410 411 public IntPtr GetCertByName(string Name)412 {413 IntPtr certContext = IntPtr.Zero;414 415 if (!_open)416 {417 throw Marshal.GetExceptionForHR(418 SMASecurity.NativeMethods.CRYPT_E_NOT_FOUND);419 }420 421 if (Valid)422 {423 if (DownLevelHelper.HashLookupSupported())424 {425 certContext = SMASecurity.NativeMethods.CertFindCertificateInStore(426 _storeHandle.Handle,427 SMASecurity.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING,428 0, // dwFindFlags429 SMASecurity.NativeMethods.CertFindType.CERT_FIND_HASH_STR,430 Name,431 IntPtr.Zero); // pPrevCertContext432 }433 else434 {435 //436 // the pre-Win8 CAPI2 code does not provide an easy way437 // to directly access a specific certificate.438 // We have to iterate through all certs to find439 // what we want.440 //441 442 while (true)443 {444 certContext = GetNextCert(certContext);445 if (certContext == IntPtr.Zero)446 {447 break;448 }449 450 X509Certificate2 cert = new(certContext);451 if (string.Equals(452 cert.Thumbprint,453 Name,454 StringComparison.OrdinalIgnoreCase))455 {456 break;457 }458 }459 }460 }461 462 return certContext;463 }464 465 public void FreeCert(IntPtr certContext)466 {467 SMASecurity.NativeMethods.CertFreeCertificateContext(certContext);468 }469 470 /// <summary>471 /// Native IntPtr store handle.472 /// </summary>473 public IntPtr StoreHandle474 {475 get476 {477 return _storeHandle.Handle;478 }479 }480 481 /// <summary>482 /// X509StoreLocation store location.483 /// </summary>484 public X509StoreLocation Location485 {486 get487 {488 return _storeLocation;489 }490 }491 492 /// <summary>493 /// String store name.494 /// </summary>495 public string StoreName496 {497 get498 {499 return _storeName;500 }501 }502 503 /// <summary>504 /// True if a real store is open.505 /// </summary>506 public bool Valid507 {508 get509 {510 return _valid;511 }512 }513 514 private bool _archivedCerts = false;515 private readonly X509StoreLocation _storeLocation = null;516 private readonly string _storeName = null;517 private CertificateStoreHandle _storeHandle = null;518 private bool _valid = false;519 private bool _open = false;520 }521 522 /// <summary>523 /// Defines the types of items524 /// supported by the certificate provider.525 /// </summary>526 internal enum CertificateProviderItem527 {528 /// <summary>529 /// An unknown item.530 /// </summary>531 Unknown,532 533 /// <summary>534 /// An X509 Certificate.535 /// </summary>536 Certificate,537 538 /// <summary>539 /// A certificate store location.540 /// For example, cert:\CurrentUser.541 /// </summary>542 Store,543 544 /// <summary>545 /// A certificate store.546 /// For example, cert:\CurrentUser\My.547 /// </summary>548 StoreLocation549 }550 551 /// <summary>552 /// Defines the implementation of a Certificate Store Provider. This provider553 /// allows for stateless namespace navigation of the computer's certificate554 /// store.555 /// </summary>556 [CmdletProvider("Certificate", ProviderCapabilities.ShouldProcess)]557 [OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)]558 [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)]559 [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PopLocation)]560 [OutputType(typeof(Microsoft.PowerShell.Commands.X509StoreLocation), typeof(X509Certificate2), ProviderCmdlet = ProviderCmdlet.GetItem)]561 [OutputType(typeof(X509Store), typeof(X509Certificate2), ProviderCmdlet = ProviderCmdlet.GetChildItem)]562 public sealed class CertificateProvider : NavigationCmdletProvider, ICmdletProviderSupportsHelp563 {564 #region tracer565 566 /// <summary>567 /// Tracer for certificate provider.568 /// </summary>569 [TraceSource("CertificateProvider",570 "The core command provider for certificates")]571 private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("CertificateProvider",572 "The core command provider for certificates");573 574 #endregion tracer575 576 /// <summary>577 /// Indicate if we already have attempted to load the PKI module.578 /// </summary>579 private bool _hasAttemptedToLoadPkiModule = false;580 581 /// <summary>582 /// Lock that guards access to the following static members583 /// -- storeLocations584 /// -- pathCache.585 /// </summary>586 private static readonly object s_staticLock = new();587 588 /// <summary>589 /// List of store locations. They do not change once initialized.590 ///591 /// Synchronized on staticLock.592 /// </summary>593 private static List<X509StoreLocation> s_storeLocations = null;594 595 /// <summary>596 /// Cache that stores paths and their associated objects.597 ///598 /// key is full path to store-location/store/certificate599 /// value is X509StoreLocation/X509NativeStore/X509Certificate2 object600 ///601 /// Synchronized on staticLock.602 /// </summary>603 private static Hashtable s_pathCache = null;604 605 /// <summary>606 /// We allow either / or \ to be the path separator.607 /// </summary>608 private static readonly char[] s_pathSeparators = new char[] { '/', '\\' };609 610 /// <summary>611 /// Regex pattern that defines a valid cert path.612 /// </summary>613 private const string certPathPattern = @"^\\((?<StoreLocation>CurrentUser|LocalMachine)(\\(?<StoreName>[a-zA-Z]+)(\\(?<Thumbprint>[0-9a-f]{40}))?)?)?$";614 615 /// <summary>616 /// Cache the store handle to avoid repeated CertOpenStore calls.617 /// </summary>618 private static X509NativeStore s_storeCache = null;619 620 /// <summary>621 /// On demand create the Regex to avoid a hit to startup perf.622 /// </summary>623 /// <remarks>624 /// Note, its OK that staticLock is being used here because only625 /// IsValidPath is calling this static property so we shouldn't626 /// have any deadlocks due to other locked static members calling627 /// this property.628 /// </remarks>629 private static Regex s_certPathRegex = null;630 631 private static Regex CertPathRegex632 {633 get634 {635 lock (s_staticLock)636 {637 if (s_certPathRegex == null)638 {639 const RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Compiled;640 s_certPathRegex = new Regex(certPathPattern, options);641 }642 }643 644 return s_certPathRegex;645 }646 }647 648 /// <summary>649 /// Initializes a new instance of the CertificateProvider class.650 /// This initializes the default certificate store locations.651 /// </summary>652 public CertificateProvider()653 {654 //655 // initialize storeLocations list and also update the cache656 //657 lock (s_staticLock)658 {659 if (s_storeLocations == null)660 {661 s_pathCache = new Hashtable(StringComparer.OrdinalIgnoreCase);662 s_storeLocations =663 new List<X509StoreLocation>();664 665 //666 // create and cache CurrentUser store-location667 //668 X509StoreLocation user = new(StoreLocation.CurrentUser);669 s_storeLocations.Add(user);670 AddItemToCache(nameof(StoreLocation.CurrentUser),671 user);672 673 //674 // create and cache LocalMachine store-location675 //676 X509StoreLocation machine = new(StoreLocation.LocalMachine);677 s_storeLocations.Add(machine);678 AddItemToCache(nameof(StoreLocation.LocalMachine),679 machine);680 681 AddItemToCache(string.Empty, s_storeLocations);682 }683 }684 }685 686 /// <summary>687 /// Removes an item at the specified path.688 /// </summary>689 /// <param name="path">690 /// The path of the item to remove.691 /// </param>692 /// <param name="recurse">693 /// Recursively remove.694 /// </param>695 /// <returns>696 /// Nothing.697 /// </returns>698 /// <exception cref="System.ArgumentException">699 /// path is null or empty.700 /// destination is null or empty.701 /// </exception>702 protected override void RemoveItem(703 string path,704 bool recurse)705 {706 path = NormalizePath(path);707 bool isContainer = false;708 bool fDeleteKey = false;709 710 object outObj = GetItemAtPath(path, false, out isContainer);711 string[] pathElements = GetPathElements(path);712 713 bool fUserContext = string.Equals(pathElements[0], "CurrentUser", StringComparison.OrdinalIgnoreCase);714 715 // isContainer = true means not a valid certificate716 717 // if source store is user root store and UI is not allowed718 // we raise invalid operation719 if (DetectUIHelper.GetOwnerWindow(Host) == IntPtr.Zero && fUserContext &&720 string.Equals(pathElements[1], "ROOT", StringComparison.OrdinalIgnoreCase))721 {722 string message = CertificateProviderStrings.UINotAllowed;723 const string errorId = "UINotAllowed";724 ThrowInvalidOperation(errorId, message);725 }726 727 if (DynamicParameters != null && DynamicParameters is ProviderRemoveItemDynamicParameters dp)728 {729 if (dp.DeleteKey)730 {731 fDeleteKey = true;732 }733 }734 735 if (isContainer)736 {737 if (pathElements.Length == 2) // is a store738 {739 // not support user context740 if (fUserContext)741 {742 string message = CertificateProviderStrings.CannotDeleteUserStore;743 const string errorId = "CannotDeleteUserStore";744 ThrowInvalidOperation(errorId, message);745 }746 747 RemoveCertStore(pathElements[1], fDeleteKey, path);748 return;749 }750 else // other container than a store751 {752 string message = CertificateProviderStrings.CannotRemoveContainer;753 const string errorId = "CannotRemoveContainer";754 ThrowInvalidOperation(errorId, message);755 }756 }757 else // certificate758 {759 // do remove760 X509Certificate2 certificate = outObj as X509Certificate2;761 RemoveCertItem(certificate, fDeleteKey, !fUserContext, path);762 return;763 }764 }765 766 /// <summary>767 /// Gets the dynamic parameters for remove-item on the Certificate768 /// Provider. We currently only support one dynamic parameter,769 /// "DeleteKey," that delete private key when we delete a certificate.770 /// </summary>771 /// <param name="path">772 /// If the path was specified on the command line, this is the path773 /// to the item for which to get the dynamic parameters.774 /// </param>775 /// <param name="recurse">776 /// Ignored.777 /// </param>778 /// <returns>779 /// An object that has properties and fields decorated with780 /// parsing attributes similar to a cmdlet class.781 /// </returns>782 protected override object RemoveItemDynamicParameters(string path, bool recurse)783 {784 return new ProviderRemoveItemDynamicParameters();785 }786 787 /// <summary>788 /// Moves an item at the specified path to the given destination.789 /// </summary>790 /// <param name="path">791 /// The path of the item to move.792 /// </param>793 /// <param name="destination">794 /// The path of the destination.795 /// </param>796 /// <returns>797 /// Nothing. Moved items are written to the context's pipeline.798 /// </returns>799 /// <exception cref="System.ArgumentException">800 /// path is null or empty.801 /// destination is null or empty.802 /// </exception>803 protected override void MoveItem(804 string path,805 string destination)806 {807 // normalize path808 path = NormalizePath(path);809 destination = NormalizePath(destination);810 811 // get elements from the path812 string[] pathElements = GetPathElements(path);813 string[] destElements = GetPathElements(destination);814 815 bool isContainer = false;816 object cert = GetItemAtPath(path, false, out isContainer);817 818 //819 // isContainer = true; means an invalid path820 //821 if (isContainer)822 {823 string message = CertificateProviderStrings.CannotMoveContainer;824 const string errorId = "CannotMoveContainer";825 ThrowInvalidOperation(errorId, message);826 }827 828 if (destElements.Length != 2) // not a store829 {830 // if the destination leads to the same thumbprint831 if (destElements.Length == 3 &&832 (string.Equals(pathElements[2], destElements[2], StringComparison.OrdinalIgnoreCase)))833 {834 // in this case we think of destination path as valid835 // and strip the thumbprint part836 destination = Path.GetDirectoryName(destination);837 }838 else839 {840 string message = CertificateProviderStrings.InvalidDestStore;841 const string errorId = "InvalidDestStore";842 ThrowInvalidOperation(errorId, message);843 }844 }845 846 // the second element is store location847 // we do not allow cross context move848 // we do not allow the destination store is the same as source849 850 if (!string.Equals(pathElements[0], destElements[0], StringComparison.OrdinalIgnoreCase))851 {852 string message = CertificateProviderStrings.CannotMoveCrossContext;853 const string errorId = "CannotMoveCrossContext";854 ThrowInvalidOperation(errorId, message);855 }856 857 if (string.Equals(pathElements[1], destElements[1], StringComparison.OrdinalIgnoreCase))858 {859 string message = CertificateProviderStrings.CannotMoveToSameStore;860 const string errorId = "CannotMoveToSameStore";861 ThrowInvalidOperation(errorId, message);862 }863 864 // if source or destination store is user root store and UI is not allowed865 // we raise invalid operation866 if (DetectUIHelper.GetOwnerWindow(Host) == IntPtr.Zero)867 {868 if ((string.Equals(pathElements[0], "CurrentUser", StringComparison.OrdinalIgnoreCase) &&869 string.Equals(pathElements[1], "ROOT", StringComparison.OrdinalIgnoreCase)) ||870 (string.Equals(destElements[0], "CurrentUser", StringComparison.OrdinalIgnoreCase) &&871 string.Equals(destElements[1], "ROOT", StringComparison.OrdinalIgnoreCase)))872 {873 string message = CertificateProviderStrings.UINotAllowed;874 const string errorId = "UINotAllowed";875 ThrowInvalidOperation(errorId, message);876 }877 }878 879 if (cert != null) // we get cert880 {881 // get destination store882 bool isDestContainer = false;883 object store = GetItemAtPath(destination, false, out isDestContainer);884 885 X509Certificate2 certificate = cert as X509Certificate2;886 887 if (store is X509NativeStore certstore)888 {889 certstore.Open(true);890 891 string action = CertificateProviderStrings.Action_Move;892 string resource = string.Format(893 CultureInfo.CurrentCulture,894 CertificateProviderStrings.MoveItemTemplate,895 path,896 destination);897 if (ShouldProcess(resource, action))898 {899 DoMove(destination, certificate, certstore, path);900 }901 }902 }903 else904 {905 ThrowItemNotFound(path, CertificateProviderItem.Certificate);906 }907 }908 909 /// <summary>910 /// Creates a certificate store with the given path.911 /// </summary>912 /// <remarks>913 /// New-Item doesn't go through the method "ItemExists". But for the914 /// CertificateProvider, New-Item can create an X509Store and return915 /// it, and the user can access the certificates within the store via its916 /// property "Certificates". We want the extra new properties of the917 /// X509Certificate2 objects to be shown to the user, so we also need918 /// to import the PKI module in this method, if we haven't tried it yet.919 /// </remarks>920 /// <param name="path">921 /// The path of the certificate store to create.922 /// </param>923 /// <param name="type">924 /// Ignored.925 /// Only support store.926 /// </param>927 /// <param name="value">928 /// Ignored929 /// </param>930 /// <returns>931 /// Nothing. The new certificate store object is932 /// written to the context's pipeline.933 /// </returns>934 /// <exception cref="System.ArgumentException">935 /// path is null or empty.936 /// </exception>937 protected override void NewItem(938 string path,939 string type,940 object value)941 {942 if (!_hasAttemptedToLoadPkiModule)943 {944 // Attempt to load the PKI module if we haven't tried yet945 AttemptToImportPkiModule();946 }947 948 path = NormalizePath(path);949 950 // get the elements from the path951 string[] pathElements = GetPathElements(path);952 953 // only support creating store954 if (pathElements.Length != 2)955 {956 string message = CertificateProviderStrings.CannotCreateItem;957 const string errorId = "CannotCreateItem";958 ThrowInvalidOperation(errorId, message);959 }960 961 bool fUserContext = string.Equals(pathElements[0], "CurrentUser", StringComparison.OrdinalIgnoreCase);962 963 // not support user context964 if (fUserContext)965 {966 string message = CertificateProviderStrings.CannotCreateUserStore;967 const string errorId = "CannotCreateUserStore";968 ThrowInvalidOperation(errorId, message);969 }970 971 const SMASecurity.NativeMethods.CertOpenStoreFlags StoreFlags =972 SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_CREATE_NEW_FLAG |973 SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_STORE_MAXIMUM_ALLOWED_FLAG |974 SMASecurity.NativeMethods.CertOpenStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE;975 976 // Create new store977 IntPtr hCertStore = SMASecurity.NativeMethods.CertOpenStore(978 SMASecurity.NativeMethods.CertOpenStoreProvider.CERT_STORE_PROV_SYSTEM,979 SMASecurity.NativeMethods.CertOpenStoreEncodingType.X509_ASN_ENCODING,980 IntPtr.Zero, // hCryptProv981 StoreFlags,982 pathElements[1]);983 if (hCertStore == IntPtr.Zero)984 {985 throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());986 }987 else // free native store handle988 {989 bool fResult = false;990 fResult = SMASecurity.NativeMethods.CertCloseStore(hCertStore, 0);991 }992 993 X509Store outStore = new(pathElements[1], StoreLocation.LocalMachine);994 WriteItemObject(outStore, path, true);995 }996 997 #region DriveCmdletProvider overrides998 999 /// <summary>1000 /// Initializes the cert: drive.1001 /// </summary>1002 /// <returns>1003 /// A collection that contains the PSDriveInfo object1004 /// that represents the cert: drive.1005 /// </returns>1006 protected override Collection<PSDriveInfo> InitializeDefaultDrives()1007 {1008 string providerDescription = CertificateProviderStrings.CertProvidername;1009 1010 PSDriveInfo drive = new(1011 name: "Cert",1012 provider: ProviderInfo,1013 root: @"\",1014 providerDescription,1015 credential: null);1016 1017 Collection<PSDriveInfo> drives = new();1018 drives.Add(drive);1019 1020 return drives;1021 }1022 1023 /// <summary>1024 /// Determines if the item at the given path is a store-location1025 /// or store with items in it.1026 /// </summary>1027 /// <param name="path">1028 /// The full path to the item.1029 /// </param>1030 /// <returns>1031 /// True if the path refers to a store location, or store that contains1032 /// certificates. False otherwise.1033 /// </returns>1034 /// <exception cref="System.ArgumentNullException">1035 /// Path is null1036 /// </exception>1037 /// <exception cref="System.Security.Cryptography.CryptographicException">1038 /// This exception can be thrown if any cryptographic error occurs.1039 /// It is not possible to know exactly what went wrong.1040 /// This is because of the way CryptographicException is designed.1041 /// Some example reasons include:1042 /// -- certificate is invalid1043 /// -- certificate has no private key1044 /// -- certificate password mismatch1045 /// </exception>1046 protected override bool HasChildItems(string path)1047 {1048 bool result = false;1049 1050 Utils.CheckArgForNull(path, "path");1051 1052 path = NormalizePath(path);1053 1054 if (path.Length == 0)1055 {1056 return true;1057 }1058 1059 bool isContainer = false;1060 1061 object item = GetItemAtPath(path, false, out isContainer);1062 1063 if ((item != null) && isContainer)1064 {1065 if (item is X509StoreLocation storeLocation)1066 {1067 result = storeLocation.StoreNames.Count > 0;1068 }1069 else if (item is X509NativeStore store)1070 {1071 store.Open(IncludeArchivedCerts());1072 IntPtr certContext = store.GetFirstCert();1073 if (certContext != IntPtr.Zero)1074 {1075 store.FreeCert(certContext);1076 result = true;1077 }1078 }1079 }1080 1081 return result;1082 }1083 1084 /// <summary>1085 /// Determines if the specified path is syntactically and semantically valid.1086 /// An example path looks like this:1087 /// cert:\CurrentUser\My\5F98EBBFE735CDDAE00E33E0FD69050EF9220254.1088 /// </summary>1089 /// <param name="path">1090 /// The path of the item to check.1091 /// </param>1092 /// <returns>1093 /// True if the path is valid, false otherwise.1094 /// </returns>1095 protected override bool IsValidPath(string path)1096 {1097 path = NormalizePath(path);1098 path = EnsureDriveIsRooted(path);1099 1100 bool isCertPath = CertPathRegex.Match(path).Success;1101 1102 return isCertPath;1103 }1104 1105 /// <summary>1106 /// Determines if the store location, store, or certificate exists1107 /// at the specified path.1108 /// </summary>1109 /// <remarks>1110 /// The method ItemExists will be hit by all built-in cmdlets that interact1111 /// with the CertificateProvider except for the New-Item. They are:1112 /// Get-ChildItem1113 /// Set-Location1114 /// Push-Location1115 /// Pop-Location1116 /// Move-Item1117 /// Invoke-Item1118 /// Get-Item1119 /// Remove-Item1120 /// So we import the PKI module in this method if we haven't tried yet.1121 /// </remarks>1122 /// <param name="path">1123 /// The path of the item to check.1124 /// </param>1125 /// <returns>1126 /// True if a the store location, store, or certificate exists1127 /// at the specified path. False otherwise.1128 /// </returns>1129 /// <exception cref="System.ArgumentNullException">1130 /// Path is null1131 /// </exception>1132 /// <exception cref="System.Security.Cryptography.CryptographicException">1133 /// This exception can be thrown if any cryptographic error occurs.1134 /// It is not possible to know exactly what went wrong.1135 /// This is because of the way CryptographicException is designed.1136 /// Possible reasons:1137 /// -- certificate is invalid1138 /// -- certificate has no private key1139 /// -- certificate password mismatch1140 /// -- etc1141 /// </exception>1142 protected override bool ItemExists(string path)1143 {1144 if (!_hasAttemptedToLoadPkiModule)1145 {1146 // Attempt to load the PKI module if we haven't tried yet1147 AttemptToImportPkiModule();1148 }1149 1150 Utils.CheckArgForNull(path, "path");1151 bool result = false;1152 bool isContainer = false;1153 object item = null;1154 1155 path = NormalizePath(path);1156 1157 if (path.Length == 0)1158 {1159 result = true;1160 }1161 else1162 {1163 //1164 // We fetch the item to see if it exists. This is1165 // because the managed cert infrastructure does not1166 // provide a way to test for existence.1167 //1168 try1169 {1170 item = GetItemAtPath(path, true, out isContainer);1171 }1172 catch (ProviderInvocationException e)1173 {1174 //1175 // if the item is not found, we get ProviderInvocationException1176 // with inner exception set to CertificateProviderItemNotFoundException1177 // If the inner exception is not of that type1178 // then we need to rethrow1179 //1180 if (e.InnerException is not CertificateProviderItemNotFoundException)1181 {1182 throw;1183 }1184 }1185 1186 result = (bool)item;1187 }1188 1189 s_tracer.WriteLine("result = {0}", result);1190 return result;1191 }1192 1193 /// <summary>1194 /// Gets the store location, store, or certificate1195 /// at the specified path.1196 /// </summary>1197 /// <param name="path">1198 /// The path of the item to retrieve.1199 /// </param>1200 /// <exception cref="System.ArgumentNullException">