MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#pragma warning disable 1634, 16915#pragma warning disable 565236 7#if !UNIX8using Microsoft.Security.Extensions;9#endif10using System.ComponentModel;11using System.IO;12using System.Management.Automation.Internal;13using System.Management.Automation.Security;14using System.Management.Automation.Win32Native;15using System.Runtime.InteropServices;16using System.Security.Cryptography.X509Certificates;17 18using Dbg = System.Management.Automation;19 20namespace System.Management.Automation21{22 /// <summary>23 /// Defines the options that control what data is embedded in the24 /// signature blob.25 /// </summary>26 public enum SigningOption27 {28 /// <summary>29 /// Embeds only the signer's certificate.30 /// </summary>31 AddOnlyCertificate,32 33 /// <summary>34 /// Embeds the entire certificate chain.35 /// </summary>36 AddFullCertificateChain,37 38 /// <summary>39 /// Embeds the entire certificate chain, except for the root40 /// certificate.41 /// </summary>42 AddFullCertificateChainExceptRoot,43 44 /// <summary>45 /// Default: Embeds the entire certificate chain, except for the46 /// root certificate.47 /// </summary>48 Default = AddFullCertificateChainExceptRoot49 }50 51 /// <summary>52 /// Helper functions for signature functionality.53 /// </summary>54 internal static class SignatureHelper55 {56 private static Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 = new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE");57 58 /// <summary>59 /// Tracer for SignatureHelper.60 /// </summary>61 [Dbg.TraceSource("SignatureHelper",62 "tracer for SignatureHelper")]63 private static readonly Dbg.PSTraceSource s_tracer =64 Dbg.PSTraceSource.GetTracer("SignatureHelper",65 "tracer for SignatureHelper");66 67 /// <summary>68 /// Sign a file.69 /// </summary>70 /// <param name="option">Option that controls what gets embedded in the signature blob.</param>71 /// <param name="fileName">Name of file to sign.</param>72 /// <param name="certificate">Signing cert.</param>73 /// <param name="timeStampServerUrl">URL of time stamping server.</param>74 /// <param name="hashAlgorithm"> The name of the hash75 /// algorithm to use.</param>76 /// <returns>Does not return a value.</returns>77 /// <exception cref="System.ArgumentNullException">78 /// Thrown if argument fileName or certificate is null.79 /// </exception>80 /// <exception cref="System.ArgumentException">81 /// Thrown if82 /// -- argument fileName is empty OR83 /// -- the specified certificate is not suitable for84 /// signing code85 /// </exception>86 /// <exception cref="System.Security.Cryptography.CryptographicException">87 /// This exception can be thrown if any cryptographic error occurs.88 /// It is not possible to know exactly what went wrong.89 /// This is because of the way CryptographicException is designed.90 /// Possible reasons:91 /// -- certificate is invalid92 /// -- certificate has no private key93 /// -- certificate password mismatch94 /// -- etc95 /// </exception>96 /// <exception cref="System.IO.FileNotFoundException">97 /// Thrown if the file specified by argument fileName is not found98 /// </exception>99 internal static Signature SignFile(SigningOption option,100 string fileName,101 X509Certificate2 certificate,102 string timeStampServerUrl,103 string hashAlgorithm)104 {105 bool result = false;106 Signature signature = null;107 IntPtr pSignInfo = IntPtr.Zero;108 uint error = 0;109 string hashOid = null;110 111 Utils.CheckArgForNullOrEmpty(fileName, "fileName");112 Utils.CheckArgForNull(certificate, "certificate");113 114 // If given, TimeStamp server URLs must begin with http:// or https://115 if (!string.IsNullOrEmpty(timeStampServerUrl))116 {117 if ((timeStampServerUrl.Length <= 7) || (118 !timeStampServerUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&119 !timeStampServerUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase)))120 {121 throw PSTraceSource.NewArgumentException(122 nameof(certificate),123 Authenticode.TimeStampUrlRequired);124 }125 }126 127 // Validate that the hash algorithm is valid128 if (!string.IsNullOrEmpty(hashAlgorithm))129 {130 IntPtr intptrAlgorithm = Marshal.StringToHGlobalUni(hashAlgorithm);131 132 IntPtr oidPtr = NativeMethods.CryptFindOIDInfo(NativeConstants.CRYPT_OID_INFO_NAME_KEY,133 intptrAlgorithm,134 0);135 136 // If we couldn't find an OID for the hash137 // algorithm, it was invalid.138 if (oidPtr == IntPtr.Zero)139 {140 throw PSTraceSource.NewArgumentException(141 nameof(certificate),142 Authenticode.InvalidHashAlgorithm);143 }144 else145 {146 NativeMethods.CRYPT_OID_INFO oidInfo =147 Marshal.PtrToStructure<NativeMethods.CRYPT_OID_INFO>(oidPtr);148 149 hashOid = oidInfo.pszOID;150 }151 }152 153 if (!SecuritySupport.CertIsGoodForSigning(certificate))154 {155 throw PSTraceSource.NewArgumentException(156 nameof(certificate),157 Authenticode.CertNotGoodForSigning);158 }159 160 SecuritySupport.CheckIfFileExists(fileName);161 // SecurityUtils.CheckIfFileSmallerThan4Bytes(fileName);162 163 try164 {165 // CryptUI is not documented either way, but does not166 // support empty strings for the timestamp server URL.167 // It expects null, only. Instead, it randomly AVs if you168 // try.169 string timeStampServerUrlForCryptUI = null;170 if (!string.IsNullOrEmpty(timeStampServerUrl))171 {172 timeStampServerUrlForCryptUI = timeStampServerUrl;173 }174 175 //176 // first initialize the struct to pass to177 // CryptUIWizDigitalSign() function178 //179 NativeMethods.CRYPTUI_WIZ_DIGITAL_SIGN_INFO si = NativeMethods.InitSignInfoStruct(fileName,180 certificate,181 timeStampServerUrlForCryptUI,182 hashOid,183 option);184 185 pSignInfo = Marshal.AllocCoTaskMem(Marshal.SizeOf(si));186 Marshal.StructureToPtr(si, pSignInfo, false);187 188 //189 // sign the file190 //191 // The GetLastWin32Error of this is checked, but PreSharp doesn't seem to be192 // able to see that.193#pragma warning disable 56523194 result = NativeMethods.CryptUIWizDigitalSign(195 (uint)NativeMethods.CryptUIFlags.CRYPTUI_WIZ_NO_UI,196 IntPtr.Zero,197 IntPtr.Zero,198 pSignInfo,199 IntPtr.Zero);200#pragma warning restore 56523201 202 if (si.pSignExtInfo != IntPtr.Zero)203 {204 Marshal.DestroyStructure<NativeMethods.CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO>(si.pSignExtInfo);205 Marshal.FreeCoTaskMem(si.pSignExtInfo);206 }207 208 if (!result)209 {210 error = GetLastWin32Error();211 212 //213 // ISSUE-2004/05/08-kumarp : there seems to be a bug214 // in CryptUIWizDigitalSign().215 // It returns 80004005 or 80070001216 // but it signs the file correctly. Mask this error217 // till we figure out this odd behavior.218 //219 if ((error == 0x80004005) ||220 (error == 0x80070001) ||221 222 // CryptUIWizDigitalSign introduced a breaking change in Win8 to return this223 // error code (ERROR_INTERNET_NAME_NOT_RESOLVED) when you provide an invalid224 // timestamp server. It used to be 0x80070001.225 // Also masking this out so that we don't introduce a breaking change ourselves.226 (error == 0x80072EE7)227 )228 {229 result = true;230 }231 else232 {233 if (error == Win32Errors.NTE_BAD_ALGID)234 {235 throw PSTraceSource.NewArgumentException(236 nameof(certificate),237 Authenticode.InvalidHashAlgorithm);238 }239 240 s_tracer.TraceError("CryptUIWizDigitalSign: failed: {0:x}",241 error);242 }243 }244 245 if (result)246 {247 signature = GetSignature(fileName, null);248 }249 else250 {251 signature = new Signature(fileName, (uint)error);252 }253 }254 finally255 {256 Marshal.DestroyStructure<NativeMethods.CRYPTUI_WIZ_DIGITAL_SIGN_INFO>(pSignInfo);257 Marshal.FreeCoTaskMem(pSignInfo);258 }259 260 return signature;261 }262 263 /// <summary>264 /// Get signature on the specified file.265 /// </summary>266 /// <param name="fileName">Name of file to check.</param>267 /// <param name="fileContent">Content of file to check.</param>268 /// <returns>Signature object.</returns>269 /// <exception cref="System.ArgumentException">270 /// Thrown if argument fileName is empty.271 /// </exception>272 /// <exception cref="System.ArgumentNullException">273 /// Thrown if argument fileName is null274 /// </exception>275 /// <exception cref="System.IO.FileNotFoundException">276 /// Thrown if the file specified by argument fileName is not found.277 /// </exception>278 internal static Signature GetSignature(string fileName, byte[] fileContent)279 {280 Signature signature = null;281 282 if (fileContent == null)283 {284 // First, try to get the signature from the latest dotNet signing API.285 signature = GetSignatureFromMSSecurityExtensions(fileName);286 }287 288 // If there is no signature or it is invalid, go by the file content289 // with the older WinVerifyTrust APIs.290 if ((signature == null) || (signature.Status != SignatureStatus.Valid))291 {292 signature = GetSignatureFromWinVerifyTrust(fileName, fileContent);293 }294 295 return signature;296 }297 298 /// <summary>299 /// Gets the file signature using the dotNet Microsoft.Security.Extensions package.300 /// This supports both Windows catalog file signatures and embedded file signatures.301 /// But it is not supported on all Windows platforms/skus, noteably Win7 and nanoserver.302 /// </summary>303 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]304 private static Signature GetSignatureFromMSSecurityExtensions(string filename)305 {306#if UNIX307 return null;308#else309 if (Signature.CatalogApiAvailable.HasValue && !Signature.CatalogApiAvailable.Value)310 {311 return null;312 }313 314 Utils.CheckArgForNullOrEmpty(filename, "fileName");315 SecuritySupport.CheckIfFileExists(filename);316 317 Signature signature = null;318 FileSignatureInfo fileSigInfo;319 using (FileStream fileStream = File.OpenRead(filename))320 {321 try322 {323 fileSigInfo = FileSignatureInfo.GetFromFileStream(fileStream);324 System.Diagnostics.Debug.Assert(fileSigInfo is not null, "Returned FileSignatureInfo should never be null.");325 }326 catch (Exception)327 {328 // For any API error, enable fallback to WinVerifyTrust APIs.329 Signature.CatalogApiAvailable = false;330 return null;331 }332 }333 334 uint error = GetErrorFromSignatureState(fileSigInfo.State);335 336 if (fileSigInfo.SigningCertificate is null)337 {338 signature = new Signature(filename, error);339 }340 else341 {342 signature = fileSigInfo.TimestampCertificate is null ?343 new Signature(filename, error, fileSigInfo.SigningCertificate) :344 new Signature(filename, error, fileSigInfo.SigningCertificate, fileSigInfo.TimestampCertificate);345 }346 347 switch (fileSigInfo.Kind)348 {349 case SignatureKind.None:350 signature.SignatureType = SignatureType.None;351 break;352 353 case SignatureKind.Embedded:354 signature.SignatureType = SignatureType.Authenticode;355 break;356 357 case SignatureKind.Catalog:358 signature.SignatureType = SignatureType.Catalog;359 break;360 361 default:362 System.Diagnostics.Debug.Fail("Signature type can only be None, Authenticode or Catalog.");363 break;364 }365 366 signature.IsOSBinary = fileSigInfo.IsOSBinary;367 368 if (signature.SignatureType == SignatureType.Catalog && !Signature.CatalogApiAvailable.HasValue)369 {370 Signature.CatalogApiAvailable = fileSigInfo.State != SignatureState.Invalid;371 }372 373 return signature;374#endif375 }376 377#if !UNIX378 private static uint GetErrorFromSignatureState(SignatureState signatureState)379 {380 switch (signatureState)381 {382 case SignatureState.Unsigned:383 return Win32Errors.TRUST_E_NOSIGNATURE;384 385 case SignatureState.SignedAndTrusted:386 return Win32Errors.NO_ERROR;387 388 case SignatureState.SignedAndNotTrusted:389 return Win32Errors.TRUST_E_EXPLICIT_DISTRUST;390 391 case SignatureState.Invalid:392 return Win32Errors.TRUST_E_BAD_DIGEST;393 394 default:395 System.Diagnostics.Debug.Fail("Should not get here - could not map FileSignatureInfo.State");396 return Win32Errors.TRUST_E_NOSIGNATURE;397 }398 }399#endif400 401 private static Signature GetSignatureFromWinVerifyTrust(string fileName, byte[] fileContent)402 {403 Signature signature = null;404 405 WinTrustMethods.WINTRUST_DATA wtd;406 uint error = Win32Errors.E_FAIL;407 408 if (fileContent == null)409 {410 Utils.CheckArgForNullOrEmpty(fileName, "fileName");411 SecuritySupport.CheckIfFileExists(fileName);412 413 // SecurityUtils.CheckIfFileSmallerThan4Bytes(fileName);414 }415 416 try417 {418 error = GetWinTrustData(fileName, fileContent, out wtd);419 420 if (error != Win32Errors.NO_ERROR)421 {422 s_tracer.WriteLine("GetWinTrustData failed: {0:x}", error);423 }424 425 signature = GetSignatureFromWintrustData(fileName, error, wtd);426 427 wtd.dwStateAction = WinTrustAction.WTD_STATEACTION_CLOSE;428 error = WinTrustMethods.WinVerifyTrust(429 IntPtr.Zero,430 ref WINTRUST_ACTION_GENERIC_VERIFY_V2,431 ref wtd);432 433 if (error != Win32Errors.NO_ERROR)434 {435 s_tracer.WriteLine("DestroyWinTrustDataStruct failed: {0:x}", error);436 }437 }438 catch (AccessViolationException)439 {440 signature = new Signature(fileName, Win32Errors.TRUST_E_NOSIGNATURE);441 }442 443 return signature;444 }445 446 private static uint GetWinTrustData(447 string fileName,448 byte[] fileContent,449 out WinTrustMethods.WINTRUST_DATA wtData)450 {451 wtData = new()452 {453 cbStruct = (uint)Marshal.SizeOf<WinTrustMethods.WINTRUST_DATA>(),454 dwUIChoice = WinTrustUIChoice.WTD_UI_NONE,455 dwStateAction = WinTrustAction.WTD_STATEACTION_VERIFY,456 };457 458 unsafe459 {460 fixed (char* fileNamePtr = fileName)461 {462 if (fileContent == null)463 {464 WinTrustMethods.WINTRUST_FILE_INFO wfi = new()465 {466 cbStruct = (uint)Marshal.SizeOf<WinTrustMethods.WINTRUST_FILE_INFO>(),467 pcwszFilePath = fileNamePtr,468 };469 wtData.dwUnionChoice = WinTrustUnionChoice.WTD_CHOICE_FILE;470 wtData.pChoice = &wfi;471 472 return WinTrustMethods.WinVerifyTrust(473 IntPtr.Zero,474 ref WINTRUST_ACTION_GENERIC_VERIFY_V2,475 ref wtData);476 }477 478 fixed (byte* contentPtr = fileContent)479 {480 Guid pwshSIP = new("603BCC1F-4B59-4E08-B724-D2C6297EF351");481 WinTrustMethods.WINTRUST_BLOB_INFO wbi = new()482 {483 cbStruct = (uint)Marshal.SizeOf<WinTrustMethods.WINTRUST_BLOB_INFO>(),484 gSubject = pwshSIP,485 pcwszDisplayName = fileNamePtr,486 cbMemObject = (uint)fileContent.Length,487 pbMemObject = contentPtr,488 };489 wtData.dwUnionChoice = WinTrustUnionChoice.WTD_CHOICE_BLOB;490 wtData.pChoice = &wbi;491 492 return WinTrustMethods.WinVerifyTrust(493 IntPtr.Zero,494 ref WINTRUST_ACTION_GENERIC_VERIFY_V2,495 ref wtData);496 }497 }498 }499 }500 501 private static X509Certificate2 GetCertFromChain(IntPtr pSigner)502 {503 try504 {505 IntPtr pCert = WinTrustMethods.WTHelperGetProvCertFromChain(pSigner, 0);506 NativeMethods.CRYPT_PROVIDER_CERT provCert =507 Marshal.PtrToStructure<NativeMethods.CRYPT_PROVIDER_CERT>(pCert);508 return new X509Certificate2(provCert.pCert);509 }510 catch (Win32Exception)511 {512 // We don't care about the Win32 error code here, so return513 // null on a failure and let the caller handle it.514 return null;515 }516 }517 518 private static Signature GetSignatureFromWintrustData(519 string filePath,520 uint error,521 WinTrustMethods.WINTRUST_DATA wtd)522 {523 s_tracer.WriteLine("GetSignatureFromWintrustData: error: {0}", error);524 525 Signature signature = null;526 if (TryGetProviderSigner(wtd.hWVTStateData, out IntPtr pProvSigner, out X509Certificate2 timestamperCert))527 {528 //529 // get cert of the signer530 //531 X509Certificate2 signerCert = GetCertFromChain(pProvSigner);532 533 if (signerCert != null)534 {535 if (timestamperCert != null)536 {537 signature = new Signature(filePath,538 error,539 signerCert,540 timestamperCert);541 }542 else543 {544 signature = new Signature(filePath,545 error,546 signerCert);547 }548 549 signature.SignatureType = SignatureType.Authenticode;550 }551 }552 553 Diagnostics.Assert(error != 0 || signature != null, "GetSignatureFromWintrustData: general crypto failure");554 555 if ((signature == null) && (error != 0))556 {557 signature = new Signature(filePath, error);558 }559 560 return signature;561 }562 563 private static bool TryGetProviderSigner(IntPtr wvtStateData, out IntPtr pProvSigner, out X509Certificate2 timestamperCert)564 {565 pProvSigner = IntPtr.Zero;566 timestamperCert = null;567 568 try569 {570 IntPtr pProvData = WinTrustMethods.WTHelperProvDataFromStateData(wvtStateData);571 572 pProvSigner = WinTrustMethods.WTHelperGetProvSignerFromChain(573 pProvData,574 signerIdx: 0,575 counterSigner: false,576 counterSignerIdx: 0);577 578 NativeMethods.CRYPT_PROVIDER_SGNR provSigner =579 Marshal.PtrToStructure<NativeMethods.CRYPT_PROVIDER_SGNR>(pProvSigner);580 if (provSigner.csCounterSigners == 1)581 {582 //583 // time stamper cert available584 //585 timestamperCert = GetCertFromChain(provSigner.pasCounterSigners);586 }587 588 return true;589 }590 catch (Win32Exception)591 {592 return false;593 }594 }595 596 private static uint GetLastWin32Error()597 {598 int error = Marshal.GetLastWin32Error();599 600 return SecuritySupport.GetDWORDFromInt(error);601 }602 }603}604 605#pragma warning restore 56523606 