#pragma warning disable 1634, 1691 /********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ #pragma warning disable 1634, 1691 #pragma warning disable 56523 using Dbg = System.Management.Automation; using System.IO; using System.Management.Automation.Internal; using System.Management.Automation.Security; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; #if CORECLR // Use stub for using Microsoft.PowerShell.CoreClr.Stubs; #endif using DWORD = System.UInt32; namespace System.Management.Automation { /// /// Defines the options that control what data is embedded in the /// signature blob /// /// public enum SigningOption { /// /// Embeds only the signer's certificate. /// AddOnlyCertificate, /// /// Embeds the entire certificate chain. /// AddFullCertificateChain, /// /// Embeds the entire certificate chain, except for the root /// certificate. /// AddFullCertificateChainExceptRoot, /// /// Default: Embeds the entire certificate chain, except for the /// root certificate. /// Default = AddFullCertificateChainExceptRoot } /// /// Helper functions for signature functionality /// internal static class SignatureHelper { /// /// tracer for SignatureHelper /// [Dbg.TraceSource("SignatureHelper", "tracer for SignatureHelper")] private static readonly Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("SignatureHelper", "tracer for SignatureHelper"); /// /// Sign a file /// /// /// option that controls what gets embedded in the signature blob /// /// name of file to sign /// /// signing cert /// /// URL of time stamping server /// /// The name of the hash /// algorithm to use. /// /// Does not return a value /// /// /// /// Thrown if argument fileName or certificate is null. /// /// /// /// /// Thrown if /// -- argument fileName is empty OR /// -- the specified certificate is not suitable for /// signing code /// /// /// /// /// This exception can be thrown if any cryptographic error occurs. /// It is not possible to know exactly what went wrong. /// This is because of the way CryptographicException is designed. /// Possible reasons: /// -- certificate is invalid /// -- certificate has no private key /// -- certificate password mismatch /// -- etc /// /// /// /// /// Thrown if the file specified by argument fileName is not found /// /// /// /// [ArchitectureSensitive] internal static Signature SignFile(SigningOption option, string fileName, X509Certificate2 certificate, string timeStampServerUrl, string hashAlgorithm) { bool result = false; Signature signature = null; IntPtr pSignInfo = IntPtr.Zero; DWORD error = 0; string hashOid = null; Utils.CheckArgForNullOrEmpty(fileName, "fileName"); Utils.CheckArgForNull(certificate, "certificate"); // If given, TimeStamp server URLs must begin with http:// if (!String.IsNullOrEmpty(timeStampServerUrl)) { if ((timeStampServerUrl.Length <= 7) || (timeStampServerUrl.IndexOf("http://", StringComparison.OrdinalIgnoreCase) != 0)) { throw PSTraceSource.NewArgumentException( "certificate", Authenticode.TimeStampUrlRequired); } } // Validate that the hash algorithm is valid if (!String.IsNullOrEmpty(hashAlgorithm)) { IntPtr intptrAlgorithm = Marshal.StringToHGlobalUni(hashAlgorithm); IntPtr oidPtr = NativeMethods.CryptFindOIDInfo(NativeConstants.CRYPT_OID_INFO_NAME_KEY, intptrAlgorithm, 0); // If we couldn't find an OID for the hash // algorithm, it was invalid. if (oidPtr == IntPtr.Zero) { throw PSTraceSource.NewArgumentException( "certificate", Authenticode.InvalidHashAlgorithm); } else { NativeMethods.CRYPT_OID_INFO oidInfo = ClrFacade.PtrToStructure(oidPtr); hashOid = oidInfo.pszOID; } } if (!SecuritySupport.CertIsGoodForSigning(certificate)) { throw PSTraceSource.NewArgumentException( "certificate", Authenticode.CertNotGoodForSigning); } SecuritySupport.CheckIfFileExists(fileName); //SecurityUtils.CheckIfFileSmallerThan4Bytes(fileName); try { // CryptUI is not documented either way, but does not // support empty strings for the timestamp server URL. // It expects null, only. Instead, it randomly AVs if you // try. string timeStampServerUrlForCryptUI = null; if (!String.IsNullOrEmpty(timeStampServerUrl)) { timeStampServerUrlForCryptUI = timeStampServerUrl; } // // first initialize the struct to pass to // CryptUIWizDigitalSign() function // NativeMethods.CRYPTUI_WIZ_DIGITAL_SIGN_INFO si = NativeMethods.InitSignInfoStruct(fileName, certificate, timeStampServerUrlForCryptUI, hashOid, option); pSignInfo = Marshal.AllocCoTaskMem(Marshal.SizeOf(si)); Marshal.StructureToPtr(si, pSignInfo, false); // // sign the file // // The GetLastWin32Error of this is checked, but PreSharp doesn't seem to be // able to see that. #pragma warning disable 56523 result = NativeMethods.CryptUIWizDigitalSign( (DWORD)NativeMethods.CryptUIFlags.CRYPTUI_WIZ_NO_UI, IntPtr.Zero, IntPtr.Zero, pSignInfo, IntPtr.Zero); #pragma warning enable 56523 if (si.pSignExtInfo != null) { ClrFacade.DestroyStructure(si.pSignExtInfo); Marshal.FreeCoTaskMem(si.pSignExtInfo); } if (!result) { error = GetLastWin32Error(); // // ISSUE-2004/05/08-kumarp : there seems to be a bug // in CryptUIWizDigitalSign(). // It returns 80004005 or 80070001 // but it signs the file correctly. Mask this error // till we figure out this odd behavior. // if ((error == 0x80004005) || (error == 0x80070001) || // CryptUIWizDigitalSign introduced a breaking change in Win8 to return this // error code (ERROR_INTERNET_NAME_NOT_RESOLVED) when you provide an invalid // timestamp server. It used to be 0x80070001. // Also masking this out so that we don't introduce a breaking change ourselves. (error == 0x80072EE7) ) { result = true; } else { if (error == Win32Errors.NTE_BAD_ALGID) { throw PSTraceSource.NewArgumentException( "certificate", Authenticode.InvalidHashAlgorithm); } s_tracer.TraceError("CryptUIWizDigitalSign: failed: {0:x}", error); } } if (result) { signature = GetSignature(fileName, null); } else { signature = new Signature(fileName, (DWORD)error); } } finally { ClrFacade.DestroyStructure(pSignInfo); Marshal.FreeCoTaskMem(pSignInfo); } return signature; } /// /// Get signature on the specified file /// /// /// name of file to check /// /// content of file to check /// /// Signature object /// /// /// Thrown if argument fileName is empty. /// /// /// /// /// Thrown if argument fileName is null /// /// /// /// /// Thrown if the file specified by argument fileName is not found. /// /// /// /// [ArchitectureSensitive] internal static Signature GetSignature(string fileName, string fileContent) { Signature signature = null; if (fileContent == null) { // First, try to get the signature from the catalog signature APIs. signature = GetSignatureFromCatalog(fileName); } // If there is no signature or it is invalid, go by the file content // with the older WinVerifyTrust APIs if ((signature == null) || (signature.Status != SignatureStatus.Valid)) { signature = GetSignatureFromWinVerifyTrust(fileName, fileContent); } return signature; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] private static Signature GetSignatureFromCatalog(string filename) { if (Signature.CatalogApiAvailable.HasValue && !Signature.CatalogApiAvailable.Value) { // Signature.CatalogApiAvailable would be set to false the first time it is detected that // WTGetSignatureInfo API does not exist on the platform, or if the API is not functional on the target platform. // Just return from the function instead of revalidating. return null; } Signature signature = null; Utils.CheckArgForNullOrEmpty(filename, "fileName"); SecuritySupport.CheckIfFileExists(filename); try { using (FileStream stream = File.OpenRead(filename)) { NativeMethods.SIGNATURE_INFO sigInfo = new NativeMethods.SIGNATURE_INFO(); sigInfo.cbSize = (uint)Marshal.SizeOf(sigInfo); IntPtr ppCertContext = IntPtr.Zero; IntPtr phStateData = IntPtr.Zero; try { int hresult = NativeMethods.WTGetSignatureInfo(filename, stream.SafeFileHandle.DangerousGetHandle(), NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_SIGNED | NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_FIRST | NativeMethods.SIGNATURE_INFO_FLAGS.SIF_AUTHENTICODE_SIGNED | NativeMethods.SIGNATURE_INFO_FLAGS.SIF_BASE_VERIFICATION | NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CHECK_OS_BINARY, ref sigInfo, ref ppCertContext, ref phStateData); if (Utils.Succeeded(hresult)) { DWORD error = GetErrorFromSignatureState(sigInfo.nSignatureState); X509Certificate2 cert = null; if (ppCertContext != IntPtr.Zero) { cert = new X509Certificate2(ppCertContext); signature = new Signature(filename, error, cert); switch (sigInfo.nSignatureType) { case NativeMethods.SIGNATURE_INFO_TYPE.SIT_AUTHENTICODE: signature.SignatureType = SignatureType.Authenticode; break; case NativeMethods.SIGNATURE_INFO_TYPE.SIT_CATALOG: signature.SignatureType = SignatureType.Catalog; break; } if (sigInfo.fOSBinary == 1) { signature.IsOSBinary = true; } } else { signature = new Signature(filename, error); } if (!Signature.CatalogApiAvailable.HasValue) { string productFile = Path.Combine(Utils.GetApplicationBase(Utils.DefaultPowerShellShellID), "Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psm1"); if (signature.Status != SignatureStatus.Valid) { if (string.Equals(filename, productFile, StringComparison.OrdinalIgnoreCase)) { Signature.CatalogApiAvailable = false; } else { // ProductFile has to be Catalog signed. Hence validating // to see if the Catalog API is functional using the ProductFile. Signature productFileSignature = GetSignatureFromCatalog(productFile); Signature.CatalogApiAvailable = (productFileSignature != null && productFileSignature.Status == SignatureStatus.Valid); } } } } else { // If calling NativeMethods.WTGetSignatureInfo failed (returned a non-zero value), we still want to set Signature.CatalogApiAvailable to false. Signature.CatalogApiAvailable = false; } } finally { if (phStateData != IntPtr.Zero) { NativeMethods.FreeWVTStateData(phStateData); } if (ppCertContext != IntPtr.Zero) { NativeMethods.CertFreeCertificateContext(ppCertContext); } } } } catch (TypeLoadException) { // If we don't have WTGetSignatureInfo, don't return a Signature. Signature.CatalogApiAvailable = false; return null; } return signature; } private static DWORD GetErrorFromSignatureState(NativeMethods.SIGNATURE_STATE state) { switch (state) { case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_MISSING: return Win32Errors.TRUST_E_NOSIGNATURE; case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_UNSUPPORTED: return Win32Errors.TRUST_E_NOSIGNATURE; case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_POLICY: return Win32Errors.TRUST_E_NOSIGNATURE; case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_INVALID_CORRUPT: return Win32Errors.TRUST_E_BAD_DIGEST; case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_INVALID_POLICY: return Win32Errors.CRYPT_E_BAD_MSG; case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_VALID: return Win32Errors.NO_ERROR; case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_TRUSTED: return Win32Errors.NO_ERROR; case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNTRUSTED: return Win32Errors.TRUST_E_EXPLICIT_DISTRUST; // Should not happen default: System.Diagnostics.Debug.Fail("Should not get here - could not map SIGNATURE_STATE"); return Win32Errors.TRUST_E_NOSIGNATURE; } } private static Signature GetSignatureFromWinVerifyTrust(string fileName, string fileContent) { Signature signature = null; NativeMethods.WINTRUST_DATA wtd; DWORD error = Win32Errors.E_FAIL; if (fileContent == null) { Utils.CheckArgForNullOrEmpty(fileName, "fileName"); SecuritySupport.CheckIfFileExists(fileName); //SecurityUtils.CheckIfFileSmallerThan4Bytes(fileName); } try { error = GetWinTrustData(fileName, fileContent, out wtd); if (error != Win32Errors.NO_ERROR) { s_tracer.WriteLine("GetWinTrustData failed: {0:x}", error); } signature = GetSignatureFromWintrustData(fileName, error, wtd); error = NativeMethods.DestroyWintrustDataStruct(wtd); if (error != Win32Errors.NO_ERROR) { s_tracer.WriteLine("DestroyWinTrustDataStruct failed: {0:x}", error); } } catch (AccessViolationException) { signature = new Signature(fileName, Win32Errors.TRUST_E_NOSIGNATURE); } return signature; } [ArchitectureSensitive] private static DWORD GetWinTrustData(string fileName, string fileContent, out NativeMethods.WINTRUST_DATA wtData) { DWORD dwResult = Win32Errors.E_FAIL; IntPtr WINTRUST_ACTION_GENERIC_VERIFY_V2 = IntPtr.Zero; IntPtr wtdBuffer = IntPtr.Zero; Guid actionVerify = new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); try { WINTRUST_ACTION_GENERIC_VERIFY_V2 = Marshal.AllocCoTaskMem(Marshal.SizeOf(actionVerify)); Marshal.StructureToPtr(actionVerify, WINTRUST_ACTION_GENERIC_VERIFY_V2, false); NativeMethods.WINTRUST_DATA wtd; if (fileContent == null) { NativeMethods.WINTRUST_FILE_INFO wfi = NativeMethods.InitWintrustFileInfoStruct(fileName); wtd = NativeMethods.InitWintrustDataStructFromFile(wfi); } else { NativeMethods.WINTRUST_BLOB_INFO wbi = NativeMethods.InitWintrustBlobInfoStruct(fileName, fileContent); wtd = NativeMethods.InitWintrustDataStructFromBlob(wbi); } wtdBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(wtd)); Marshal.StructureToPtr(wtd, wtdBuffer, false); // The result is returned to the caller, and handled generically. // Disable the PreFast check for Win32 error codes, as we don't care. #pragma warning disable 56523 dwResult = NativeMethods.WinVerifyTrust( IntPtr.Zero, WINTRUST_ACTION_GENERIC_VERIFY_V2, wtdBuffer); #pragma warning enable 56523 wtData = ClrFacade.PtrToStructure(wtdBuffer); } finally { ClrFacade.DestroyStructure(WINTRUST_ACTION_GENERIC_VERIFY_V2); Marshal.FreeCoTaskMem(WINTRUST_ACTION_GENERIC_VERIFY_V2); ClrFacade.DestroyStructure(wtdBuffer); Marshal.FreeCoTaskMem(wtdBuffer); } return dwResult; } [ArchitectureSensitive] private static X509Certificate2 GetCertFromChain(IntPtr pSigner) { X509Certificate2 signerCert = null; // We don't care about the Win32 error code here, so disable // the PreFast complaint that we're not retrieving it. #pragma warning disable 56523 IntPtr pCert = NativeMethods.WTHelperGetProvCertFromChain(pSigner, 0); #pragma warning enable 56523 if (pCert != IntPtr.Zero) { NativeMethods.CRYPT_PROVIDER_CERT provCert = (NativeMethods.CRYPT_PROVIDER_CERT) ClrFacade.PtrToStructure(pCert); signerCert = new X509Certificate2(provCert.pCert); } return signerCert; } [ArchitectureSensitive] private static Signature GetSignatureFromWintrustData( string filePath, DWORD error, NativeMethods.WINTRUST_DATA wtd) { Signature signature = null; X509Certificate2 signerCert = null; X509Certificate2 timestamperCert = null; s_tracer.WriteLine("GetSignatureFromWintrustData: error: {0}", error); // The GetLastWin32Error of this is checked, but PreSharp doesn't seem to be // able to see that. #pragma warning disable 56523 IntPtr pProvData = NativeMethods.WTHelperProvDataFromStateData(wtd.hWVTStateData); #pragma warning enable 56523 if (pProvData != IntPtr.Zero) { IntPtr pProvSigner = NativeMethods.WTHelperGetProvSignerFromChain(pProvData, 0, 0, 0); if (pProvSigner != IntPtr.Zero) { // // get cert of the signer // signerCert = GetCertFromChain(pProvSigner); if (signerCert != null) { NativeMethods.CRYPT_PROVIDER_SGNR provSigner = (NativeMethods.CRYPT_PROVIDER_SGNR) ClrFacade.PtrToStructure(pProvSigner); if (provSigner.csCounterSigners == 1) { // // time stamper cert available // timestamperCert = GetCertFromChain(provSigner.pasCounterSigners); } if (timestamperCert != null) { signature = new Signature(filePath, error, signerCert, timestamperCert); } else { signature = new Signature(filePath, error, signerCert); } signature.SignatureType = SignatureType.Authenticode; } } } Diagnostics.Assert(((error == 0) && (signature != null)) || (error != 0), "GetSignatureFromWintrustData: general crypto failure"); if ((signature == null) && (error != 0)) { signature = new Signature(filePath, error); } return signature; } [ArchitectureSensitive] private static DWORD GetLastWin32Error() { int error = Marshal.GetLastWin32Error(); return SecuritySupport.GetDWORDFromInt(error); } } } #pragma warning restore 56523