如何使用PFX(Bouncy Castle或其他)以编程方式对可执行文件进行代码签名

我正在尝试确定使用Bouncy Castle,托管代码或来自C#的非托管代码对可执行文件进行代码签名的最佳方法。 由于CAPICOM现已弃用,我想mssign32.dll中的一个SignerSign方法是最好的方法,如果需要不受管理的话。

这个答案( https://stackoverflow.com/a/3952235/722078 )似乎很接近,但是会生成一个.p7m文件,虽然看起来大小合适,但是无法正常运行(显然在运行前重命名为.exe) )。

问题提供者给出的解决方案( 用于替换signtool.exe的API /库 )似乎很有前途和管理,但是像Tom Canham在下面的评论中提到的那样,“这似乎是用于签署封装消息.Authenticode – 代码 – 签署signtool所做的 – 是不同的,这就是签署后EXE不运行的原因。“ 当我使用提问者的解决方案或之前引用的Bouncy Castle解决方案签名时,我收到了Tom所犯的错误。

我还没有尝试过唯一的选择( https://stackoverflow.com/a/6429860/722078 ),虽然看起来很有希望,但我并不认为它使用“authenticode”代码签名而不是“封闭的消息“代码签名。 这个答案也有不使用现在已弃用的CAPICOM互操作方法的好处,所以我想我今天将使用这种方法报告我的结果。 如果这是最好的选择,那么有人可以说出从mssign32.dll导出的SignerSign , SignerSignEx和SignerSignEx2函数之间的区别吗? 我已经读过SignerSignEx2应该与Windows 8及更高版本一起使用…

长话短说,我想复制signtool.exe签署.exe文件,.pfx文件和密码的可执行文件的能力,如下所示:

signtool sign /f cert.pfx /p password application.exe 

我正在寻找使用authenticode签名以编程方式对可执行文件进行代码签名的最佳选项(如果重要,则使用PE),如果可能的话,我更愿意使用充气城堡或托管代码,但如果它可行,我会使用非托管的东西目前已弃用。

谢谢!

据我所知,SignSigner和SignSignerEx从Windows XP开始提供,这是我最关注的操作系统。 因为我不需要担心Windows App Store的发布,所以这个答案仅限于SignSigner和SignSignerEx,尽管SignSignerEx2的导入与SignSignerEx非常相似,我不希望它会导致任何问题。

以下类允许您通过调用以下命令使用.pfx对可执行文件进行签名:

 SignWithCert(string appPath, string certPath, string certPassword, string timestampUrl); 

它还允许您通过调用以下方式使用密钥库中的证书对可执行文件进行签名:

 SignWithThumbPrint(string appPath, string thumbprint, string timestampUrl); 

如果要使用安装在密钥库中的证书进行签名,则可能需要更新FindCertByThumbPrint(字符串thumbPrint)以检查比我要检查的密钥库更多的密钥库。 99.5%的时间,我们的客户使用.pfx签名而不是使用指纹。

为了便于说明,SignWithCert()使用SignerSignEx和SignerTimeStampEx,而SignWithThumbPrint()使用SignerSign和SignerTimeStamp。

它们很容易互换。 SignerSignEx和SignerTimeStampEx返回一个SIGNER_CONTEXT指针,允许您使用dwFlags参数修改函数的行为(如果您正在签署可移植的可执行文件)。 此处列出了有效标志选项。 基本上,如果您将dwFlags传递给SignerSignEx,则输出将与仅使用SignerSign相同。 在我的情况下,我想我将使用SignerSign,因为我不相信我需要一个指向签名者上下文的指针,无论出于何种可能的原因。

无论如何,这是class级。 这是我第一次在这里发布代码,所以我希望我没有在格式化中破坏它。

代码按预期工作,可执行文件运行正常并签名,但签名块的二进制输出与signtool.exe的二进制输出略有不同(在该测试中,两个工具都没有使用时间戳)。 我将此归因于signtool.exe似乎使用CAPICOM进行签名并使用Mssign32.dll这一事实,但总而言之,我对初始测试集中的它非常满意。

error handling显然需要改进。

感谢GregS以及之前发布过代码示例的所有人。

这是相关的东西。 当我有机会这样做时,我会用评论和改进来更新这个块。

更新1:添加了更好的error handling和注释,以及在FindCertByThumbprint(字符串指纹)中重新格式化指纹以允许在Windows 8和Windows 10(公共预览)上找到证书。 当指纹中留有空格时,这些操作系统不会返回匹配,因此我现在正在搜索之前修复它们。

 using System; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace Utilities { internal static class SignTool { #region Structures [StructLayoutAttribute(LayoutKind.Sequential)] struct SIGNER_SUBJECT_INFO { public uint cbSize; public IntPtr pdwIndex; public uint dwSubjectChoice; public SubjectChoiceUnion Union1; [StructLayoutAttribute(LayoutKind.Explicit)] internal struct SubjectChoiceUnion { [FieldOffsetAttribute(0)] public System.IntPtr pSignerFileInfo; [FieldOffsetAttribute(0)] public System.IntPtr pSignerBlobInfo; }; } [StructLayoutAttribute(LayoutKind.Sequential)] struct SIGNER_CERT { public uint cbSize; public uint dwCertChoice; public SignerCertUnion Union1; [StructLayoutAttribute(LayoutKind.Explicit)] internal struct SignerCertUnion { [FieldOffsetAttribute(0)] public IntPtr pwszSpcFile; [FieldOffsetAttribute(0)] public IntPtr pCertStoreInfo; [FieldOffsetAttribute(0)] public IntPtr pSpcChainInfo; }; public IntPtr hwnd; } [StructLayoutAttribute(LayoutKind.Sequential)] struct SIGNER_SIGNATURE_INFO { public uint cbSize; public uint algidHash; // ALG_ID public uint dwAttrChoice; public IntPtr pAttrAuthCode; public IntPtr psAuthenticated; // PCRYPT_ATTRIBUTES public IntPtr psUnauthenticated; // PCRYPT_ATTRIBUTES } [StructLayoutAttribute(LayoutKind.Sequential)] struct SIGNER_FILE_INFO { public uint cbSize; public IntPtr pwszFileName; public IntPtr hFile; } [StructLayoutAttribute(LayoutKind.Sequential)] struct SIGNER_CERT_STORE_INFO { public uint cbSize; public IntPtr pSigningCert; // CERT_CONTEXT public uint dwCertPolicy; public IntPtr hCertStore; } [StructLayoutAttribute(LayoutKind.Sequential)] struct SIGNER_CONTEXT { public uint cbSize; public uint cbBlob; public IntPtr pbBlob; } [StructLayoutAttribute(LayoutKind.Sequential)] struct SIGNER_PROVIDER_INFO { public uint cbSize; public IntPtr pwszProviderName; public uint dwProviderType; public uint dwKeySpec; public uint dwPvkChoice; public SignerProviderUnion Union1; [StructLayoutAttribute(LayoutKind.Explicit)] internal struct SignerProviderUnion { [FieldOffsetAttribute(0)] public IntPtr pwszPvkFileName; [FieldOffsetAttribute(0)] public IntPtr pwszKeyContainer; }; } #endregion #region Imports [DllImport("Mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int SignerSign( IntPtr pSubjectInfo, // SIGNER_SUBJECT_INFO IntPtr pSignerCert, // SIGNER_CERT IntPtr pSignatureInfo, // SIGNER_SIGNATURE_INFO IntPtr pProviderInfo, // SIGNER_PROVIDER_INFO string pwszHttpTimeStamp, // LPCWSTR IntPtr psRequest, // PCRYPT_ATTRIBUTES IntPtr pSipData // LPVOID ); [DllImport("Mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int SignerSignEx( uint dwFlags, // DWORD IntPtr pSubjectInfo, // SIGNER_SUBJECT_INFO IntPtr pSignerCert, // SIGNER_CERT IntPtr pSignatureInfo, // SIGNER_SIGNATURE_INFO IntPtr pProviderInfo, // SIGNER_PROVIDER_INFO string pwszHttpTimeStamp, // LPCWSTR IntPtr psRequest, // PCRYPT_ATTRIBUTES IntPtr pSipData, // LPVOID out SIGNER_CONTEXT ppSignerContext // SIGNER_CONTEXT ); [DllImport("Mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int SignerTimeStamp( IntPtr pSubjectInfo, // SIGNER_SUBJECT_INFO string pwszHttpTimeStamp, // LPCWSTR IntPtr psRequest, // PCRYPT_ATTRIBUTES IntPtr pSipData // LPVOID ); [DllImport("Mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int SignerTimeStampEx( uint dwFlags, // DWORD IntPtr pSubjectInfo, // SIGNER_SUBJECT_INFO string pwszHttpTimeStamp, // LPCWSTR IntPtr psRequest, // PCRYPT_ATTRIBUTES IntPtr pSipData, // LPVOID out SIGNER_CONTEXT ppSignerContext // SIGNER_CONTEXT ); [DllImport("Crypt32.dll", EntryPoint = "CertCreateCertificateContext", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)] private static extern IntPtr CertCreateCertificateContext( int dwCertEncodingType, byte[] pbCertEncoded, int cbCertEncoded); #endregion #region public methods // Call SignerSignEx and SignerTimeStampEx for a given .pfx public static void SignWithCert(string appPath, string certPath, string certPassword, string timestampUrl) { IntPtr pSignerCert = IntPtr.Zero; IntPtr pSubjectInfo = IntPtr.Zero; IntPtr pSignatureInfo = IntPtr.Zero; IntPtr pProviderInfo = IntPtr.Zero; try { // Grab the X509Certificate from the .pfx file. X509Certificate2 cert = new X509Certificate2(certPath, certPassword); pSignerCert = CreateSignerCert(cert); pSubjectInfo = CreateSignerSubjectInfo(appPath); pSignatureInfo = CreateSignerSignatureInfo(); pProviderInfo = GetProviderInfo(cert); SIGNER_CONTEXT signerContext; SignCode(0x0, pSubjectInfo, pSignerCert, pSignatureInfo, pProviderInfo, out signerContext); // Only attempt to timestamp if we've got a timestampUrl. if (!string.IsNullOrEmpty(timestampUrl)) { TimeStampSignedCode(0x0, pSubjectInfo, timestampUrl, out signerContext); } } catch (CryptographicException ce) { string exception; // do anything with this useful information? switch (Marshal.GetHRForException(ce)) { case -2146885623: exception = string.Format(@"An error occurred while attempting to load the signing certificate. ""{0}"" does not appear to contain a valid certificate.", certPath); break; case -2147024810: exception = string.Format(@"An error occurred while attempting to load the signing certificate. The specified password was incorrect."); break; default: exception = string.Format(@"An error occurred while attempting to load the signing certificate. {0}", ce.Message); break; } } catch (Exception e) { // do anything with this useful information? string exception = e.Message; } finally { if (pSignerCert != IntPtr.Zero) { Marshal.DestroyStructure(pSignerCert, typeof(SIGNER_CERT)); } if (pSubjectInfo != IntPtr.Zero) { Marshal.DestroyStructure(pSubjectInfo, typeof(SIGNER_SUBJECT_INFO)); } if (pSignatureInfo != IntPtr.Zero) { Marshal.DestroyStructure(pSignatureInfo, typeof(SIGNER_SIGNATURE_INFO)); } if (pProviderInfo != IntPtr.Zero) { Marshal.DestroyStructure(pSignatureInfo, typeof(SIGNER_PROVIDER_INFO)); } } } // Call SignerSign and SignerTimeStamp for a given thumbprint. public static void SignWithThumbprint(string appPath, string thumbprint, string timestampUrl) { IntPtr pSignerCert = IntPtr.Zero; IntPtr pSubjectInfo = IntPtr.Zero; IntPtr pSignatureInfo = IntPtr.Zero; IntPtr pProviderInfo = IntPtr.Zero; try { pSignerCert = CreateSignerCert(thumbprint); pSubjectInfo = CreateSignerSubjectInfo(appPath); pSignatureInfo = CreateSignerSignatureInfo(); SignCode(pSubjectInfo, pSignerCert, pSignatureInfo, pProviderInfo); // Only attempt to timestamp if we've got a timestampUrl. if (!string.IsNullOrEmpty(timestampUrl)) { TimeStampSignedCode(pSubjectInfo, timestampUrl); } } catch (CryptographicException ce) { // do anything with this useful information? string exception = string.Format(@"An error occurred while attempting to load the signing certificate. {0}", ce.Message); } catch (Exception e) { // do anything with this useful information? string exception = e.Message; } finally { if (pSignerCert != IntPtr.Zero) { Marshal.DestroyStructure(pSignerCert, typeof(SIGNER_CERT)); } if (pSubjectInfo != IntPtr.Zero) { Marshal.DestroyStructure(pSubjectInfo, typeof(SIGNER_SUBJECT_INFO)); } if (pSignatureInfo != IntPtr.Zero) { Marshal.DestroyStructure(pSignatureInfo, typeof(SIGNER_SIGNATURE_INFO)); } } } #endregion #region private methods private static IntPtr CreateSignerSubjectInfo(string pathToAssembly) { SIGNER_SUBJECT_INFO info = new SIGNER_SUBJECT_INFO { cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_SUBJECT_INFO)), pdwIndex = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(uint))) }; var index = 0; Marshal.StructureToPtr(index, info.pdwIndex, false); info.dwSubjectChoice = 0x1; //SIGNER_SUBJECT_FILE IntPtr assemblyFilePtr = Marshal.StringToHGlobalUni(pathToAssembly); SIGNER_FILE_INFO fileInfo = new SIGNER_FILE_INFO { cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_FILE_INFO)), pwszFileName = assemblyFilePtr, hFile = IntPtr.Zero }; info.Union1 = new SIGNER_SUBJECT_INFO.SubjectChoiceUnion { pSignerFileInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SIGNER_FILE_INFO))) }; Marshal.StructureToPtr(fileInfo, info.Union1.pSignerFileInfo, false); IntPtr pSubjectInfo = Marshal.AllocHGlobal(Marshal.SizeOf(info)); Marshal.StructureToPtr(info, pSubjectInfo, false); return pSubjectInfo; } private static X509Certificate2 FindCertByThumbprint(string thumbprint) { try { // Remove spaces convert to upper. Windows 10 (preview) and Windows 8 will not return a cert // unless it is a perfect match with no spaces and all uppercase characters. string thumbprintFixed = thumbprint.Replace(" ", string.Empty).ToUpperInvariant(); // Check common store locations for the corresponding code-signing cert. X509Store[] stores = new X509Store[4] { new X509Store(StoreName.My, StoreLocation.CurrentUser), new X509Store(StoreName.My, StoreLocation.LocalMachine), new X509Store(StoreName.TrustedPublisher, StoreLocation.CurrentUser), new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine) }; foreach (X509Store store in stores) { store.Open(OpenFlags.ReadOnly); // Find the cert! X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprintFixed, false); store.Close(); // If we didn't find the cert, try the next store. if (certs.Count < 1) { continue; } // Return the cert (first one if there is more than one identical cert in the collection). return certs[0]; } // No cert was found. Return null. throw new Exception(string.Format(@"A certificate matching the thumbprint: ""{0}"" could not be found. Make sure that a valid certificate matching the provided thumbprint is installed.", thumbprint)); } catch (Exception e) { throw new Exception(string.Format("{0}", e.Message)); } } private static IntPtr CreateSignerCert(X509Certificate2 cert) { SIGNER_CERT signerCert = new SIGNER_CERT { cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_CERT)), dwCertChoice = 0x2, Union1 = new SIGNER_CERT.SignerCertUnion { pCertStoreInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SIGNER_CERT_STORE_INFO))) }, hwnd = IntPtr.Zero }; const int X509_ASN_ENCODING = 0x00000001; const int PKCS_7_ASN_ENCODING = 0x00010000; IntPtr pCertContext = CertCreateCertificateContext( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, cert.GetRawCertData(), cert.GetRawCertData().Length); SIGNER_CERT_STORE_INFO certStoreInfo = new SIGNER_CERT_STORE_INFO { cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_CERT_STORE_INFO)), pSigningCert = pCertContext, dwCertPolicy = 0x2, // SIGNER_CERT_POLICY_CHAIN hCertStore = IntPtr.Zero }; Marshal.StructureToPtr(certStoreInfo, signerCert.Union1.pCertStoreInfo, false); IntPtr pSignerCert = Marshal.AllocHGlobal(Marshal.SizeOf(signerCert)); Marshal.StructureToPtr(signerCert, pSignerCert, false); return pSignerCert; } private static IntPtr CreateSignerCert(string thumbprint) { SIGNER_CERT signerCert = new SIGNER_CERT { cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_CERT)), dwCertChoice = 0x2, Union1 = new SIGNER_CERT.SignerCertUnion { pCertStoreInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SIGNER_CERT_STORE_INFO))) }, hwnd = IntPtr.Zero }; const int X509_ASN_ENCODING = 0x00000001; const int PKCS_7_ASN_ENCODING = 0x00010000; X509Certificate2 cert = FindCertByThumbprint(thumbprint); IntPtr pCertContext = CertCreateCertificateContext( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, cert.GetRawCertData(), cert.GetRawCertData().Length); SIGNER_CERT_STORE_INFO certStoreInfo = new SIGNER_CERT_STORE_INFO { cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_CERT_STORE_INFO)), pSigningCert = pCertContext, dwCertPolicy = 0x2, // SIGNER_CERT_POLICY_CHAIN hCertStore = IntPtr.Zero }; Marshal.StructureToPtr(certStoreInfo, signerCert.Union1.pCertStoreInfo, false); IntPtr pSignerCert = Marshal.AllocHGlobal(Marshal.SizeOf(signerCert)); Marshal.StructureToPtr(signerCert, pSignerCert, false); return pSignerCert; } private static IntPtr CreateSignerSignatureInfo() { SIGNER_SIGNATURE_INFO signatureInfo = new SIGNER_SIGNATURE_INFO { cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_SIGNATURE_INFO)), algidHash = 0x00008004, // CALG_SHA1 dwAttrChoice = 0x0, // SIGNER_NO_ATTR pAttrAuthCode = IntPtr.Zero, psAuthenticated = IntPtr.Zero, psUnauthenticated = IntPtr.Zero }; IntPtr pSignatureInfo = Marshal.AllocHGlobal(Marshal.SizeOf(signatureInfo)); Marshal.StructureToPtr(signatureInfo, pSignatureInfo, false); return pSignatureInfo; } private static IntPtr GetProviderInfo(X509Certificate2 cert) { if (cert == null || !cert.HasPrivateKey) { return IntPtr.Zero; } ICspAsymmetricAlgorithm key = (ICspAsymmetricAlgorithm)cert.PrivateKey; const int PVK_TYPE_KEYCONTAINER = 2; if (key == null) { return IntPtr.Zero; } SIGNER_PROVIDER_INFO providerInfo = new SIGNER_PROVIDER_INFO { cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_PROVIDER_INFO)), pwszProviderName = Marshal.StringToHGlobalUni(key.CspKeyContainerInfo.ProviderName), dwProviderType = (uint)key.CspKeyContainerInfo.ProviderType, dwPvkChoice = PVK_TYPE_KEYCONTAINER, Union1 = new SIGNER_PROVIDER_INFO.SignerProviderUnion { pwszKeyContainer = Marshal.StringToHGlobalUni(key.CspKeyContainerInfo.KeyContainerName) }, }; IntPtr pProviderInfo = Marshal.AllocHGlobal(Marshal.SizeOf(providerInfo)); Marshal.StructureToPtr(providerInfo, pProviderInfo, false); return pProviderInfo; } // Use SignerSign private static void SignCode(IntPtr pSubjectInfo, IntPtr pSignerCert, IntPtr pSignatureInfo, IntPtr pProviderInfo) { int hResult = SignerSign( pSubjectInfo, pSignerCert, pSignatureInfo, pProviderInfo, null, IntPtr.Zero, IntPtr.Zero ); if (hResult != 0) { // See if we can get anything useful. Jury's still out on this one. Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } } // Use SignerSignEx private static void SignCode(uint dwFlags, IntPtr pSubjectInfo, IntPtr pSignerCert, IntPtr pSignatureInfo, IntPtr pProviderInfo, out SIGNER_CONTEXT signerContext) { int hResult = SignerSignEx( dwFlags, pSubjectInfo, pSignerCert, pSignatureInfo, pProviderInfo, null, IntPtr.Zero, IntPtr.Zero, out signerContext ); if (hResult != 0) { // See if we can get anything useful. Jury's still out on this one. Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } } // Use SignerTimeStamp private static void TimeStampSignedCode(IntPtr pSubjectInfo, string timestampUrl) { int hResult = SignerTimeStamp( pSubjectInfo, timestampUrl, IntPtr.Zero, IntPtr.Zero ); if (hResult != 0) { // We can't get anything useful from GetHRForLastWin32Error, so let's throw our own. //Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); throw new Exception(string.Format(@"""{0}"" could not be used at this time. If necessary, check the timestampUrl, internet connection, and try again.", timestampUrl)); } } // Use SignerTimeStampEx private static void TimeStampSignedCode(uint dwFlags, IntPtr pSubjectInfo, string timestampUrl, out SIGNER_CONTEXT signerContext) { int hResult = SignerTimeStampEx( dwFlags, pSubjectInfo, timestampUrl, IntPtr.Zero, IntPtr.Zero, out signerContext ); if (hResult != 0) { // We can't get anything useful from GetHRForLastWin32Error, so let's throw our own. //Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); throw new Exception(string.Format(@"""{0}"" could not be used at this time. If necessary, check the timestampUrl, internet connection, and try again.", timestampUrl)); } } #endregion } }