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( nameof(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( nameof(certificate), Authenticode.InvalidHashAlgorithm); } else { NativeMethods.CRYPT_OID_INFO oidInfo = Marshal.PtrToStructure <NativeMethods.CRYPT_OID_INFO>(oidPtr); hashOid = oidInfo.pszOID; } } if (!SecuritySupport.CertIsGoodForSigning(certificate)) { throw PSTraceSource.NewArgumentException( nameof(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 restore 56523 if (si.pSignExtInfo != IntPtr.Zero) { Marshal.DestroyStructure <NativeMethods.CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO>(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( nameof(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 { Marshal.DestroyStructure <NativeMethods.CRYPTUI_WIZ_DIGITAL_SIGN_INFO>(pSignInfo); Marshal.FreeCoTaskMem(pSignInfo); } return(signature); }
private static DWORD GetLastWin32Error() { int error = Marshal.GetLastWin32Error(); return(SecuritySupport.GetDWORDFromInt(error)); }
private static Signature GetSignatureFromMSSecurityExtensions(string filename) { #if UNIX return(null); #else if (Signature.CatalogApiAvailable.HasValue && !Signature.CatalogApiAvailable.Value) { return(null); } Utils.CheckArgForNullOrEmpty(filename, "fileName"); SecuritySupport.CheckIfFileExists(filename); Signature signature = null; FileSignatureInfo fileSigInfo; using (FileStream fileStream = File.OpenRead(filename)) { try { fileSigInfo = FileSignatureInfo.GetFromFileStream(fileStream); System.Diagnostics.Debug.Assert(fileSigInfo is not null, "Returned FileSignatureInfo should never be null."); } catch (Exception) { // For any API error, enable fallback to WinVerifyTrust APIs. Signature.CatalogApiAvailable = false; return(null); } } DWORD error = GetErrorFromSignatureState(fileSigInfo.State); if (fileSigInfo.SigningCertificate is null) { signature = new Signature(filename, error); } else { signature = fileSigInfo.TimestampCertificate is null ? new Signature(filename, error, fileSigInfo.SigningCertificate) : new Signature(filename, error, fileSigInfo.SigningCertificate, fileSigInfo.TimestampCertificate); } switch (fileSigInfo.Kind) { case SignatureKind.None: signature.SignatureType = SignatureType.None; break; case SignatureKind.Embedded: signature.SignatureType = SignatureType.Authenticode; break; case SignatureKind.Catalog: signature.SignatureType = SignatureType.Catalog; break; default: System.Diagnostics.Debug.Fail("Signature type can only be None, Authenticode or Catalog."); break; } signature.IsOSBinary = fileSigInfo.IsOSBinary; if (signature.SignatureType == SignatureType.Catalog && !Signature.CatalogApiAvailable.HasValue) { Signature.CatalogApiAvailable = fileSigInfo.State != SignatureState.Invalid; } return(signature); #endif }
private static string GetSignatureStatusMessage(SignatureStatus status, DWORD error, string filePath) { string message = null; string resourceString = null; string arg = null; switch (status) { case SignatureStatus.Valid: resourceString = MshSignature.MshSignature_Valid; break; case SignatureStatus.UnknownError: int intError = SecuritySupport.GetIntFromDWORD(error); Win32Exception e = new Win32Exception(intError); message = e.Message; break; case SignatureStatus.Incompatible: if (error == Win32Errors.NTE_BAD_ALGID) { resourceString = MshSignature.MshSignature_Incompatible_HashAlgorithm; } else { resourceString = MshSignature.MshSignature_Incompatible; } arg = filePath; break; case SignatureStatus.NotSigned: resourceString = MshSignature.MshSignature_NotSigned; arg = filePath; break; case SignatureStatus.HashMismatch: resourceString = MshSignature.MshSignature_HashMismatch; arg = filePath; break; case SignatureStatus.NotTrusted: resourceString = MshSignature.MshSignature_NotTrusted; arg = filePath; break; case SignatureStatus.NotSupportedFileFormat: resourceString = MshSignature.MshSignature_NotSupportedFileFormat; arg = System.IO.Path.GetExtension(filePath); if (string.IsNullOrEmpty(arg)) { resourceString = MshSignature.MshSignature_NotSupportedFileFormat_NoExtension; arg = null; } break; } if (message == null) { if (arg == null) { message = resourceString; } else { message = StringUtil.Format(resourceString, arg); } } return(message); }
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); // Get the time stamper certificate if available TryGetProviderSigner(phStateData, out IntPtr pProvSigner, out X509Certificate2 timestamperCert); if (timestamperCert != null) { signature = new Signature(filename, error, cert, timestamperCert); } else { 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.DefaultPowerShellAppBase, "Modules\\PSDiagnostics\\PSDiagnostics.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 string GetSignatureStatusMessage(SignatureStatus status, uint error, string filePath) { string message = null; string formatSpec = null; string extension = null; switch (status) { case SignatureStatus.Valid: formatSpec = MshSignature.MshSignature_Valid; goto Label_00A4; case SignatureStatus.UnknownError: { Win32Exception exception = new Win32Exception(SecuritySupport.GetIntFromDWORD(error)); message = exception.Message; goto Label_00A4; } case SignatureStatus.NotSigned: formatSpec = MshSignature.MshSignature_NotSigned; extension = filePath; goto Label_00A4; case SignatureStatus.HashMismatch: formatSpec = MshSignature.MshSignature_HashMismatch; extension = filePath; goto Label_00A4; case SignatureStatus.NotTrusted: formatSpec = MshSignature.MshSignature_NotTrusted; extension = filePath; goto Label_00A4; case SignatureStatus.NotSupportedFileFormat: formatSpec = MshSignature.MshSignature_NotSupportedFileFormat; extension = System.IO.Path.GetExtension(filePath); if (string.IsNullOrEmpty(extension)) { formatSpec = MshSignature.MshSignature_NotSupportedFileFormat_NoExtension; extension = null; } goto Label_00A4; case SignatureStatus.Incompatible: if (error != 0x80090008) { formatSpec = MshSignature.MshSignature_Incompatible; break; } formatSpec = MshSignature.MshSignature_Incompatible_HashAlgorithm; break; default: goto Label_00A4; } extension = filePath; Label_00A4: if (message != null) { return(message); } if (extension == null) { return(formatSpec); } return(StringUtil.Format(formatSpec, extension)); }