我正在实现Windows的进程提升帮助程序。 这是一个运行在提升模式下的程序,并以pipe理员权限启动其他程序,而不显示其他UAC提示。 出于安全原因,我想确保只能执行使用我公司的Authenticode密钥进行数字签名的二进制文件。
WinVerifyTrust函数在那里得到了一半,但它只能确保一个二进制文件是由一些属于微软信任链的密钥签名的。 是否有一个相对简单的方式来执行validation码validation,并确保它是由我们的私钥签名?
我相信你正在寻找的是CryptQueryObject 。
有了它,你应该能够将所涉及的证书从PE中提取出来,并且执行任何额外的检查。
举个例子,这会让你到HCRYPTMSG。 从那里你可以使用CryptMsgGetParam来取出你想要的东西。 我希望能够做出更加“健壮”的东西,但是这些API非常繁琐,因为它们需要大量的分支来处理所有的返回情况。
所以,这里是ap / invoke-rific c#示例(我从C开始,但基本上是不可读的):
static class Crypt32 { //Omitting flag constants; you can look these up in WinCrypt.h [DllImport("CRYPT32.DLL", EntryPoint = "CryptQueryObject", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptQueryObject( int dwObjectType, IntPtr pvObject, int dwExpectedContentTypeFlags, int dwExpectedFormatTypeFlags, int dwFlags, out int pdwMsgAndCertEncodingType, out int pdwContentType, out int pdwFormatType, ref IntPtr phCertStore, ref IntPtr phMsg, ref IntPtr ppvContext); } class Program { static void Main(string[] args) { //Path to executable here // I tested with MS-Office .exe's string path = ""; int contentType; int formatType; int ignored; IntPtr context = IntPtr.Zero; IntPtr pIgnored = IntPtr.Zero; IntPtr cryptMsg = IntPtr.Zero; if (!Crypt32.CryptQueryObject( Crypt32.CERT_QUERY_OBJECT_FILE, Marshal.StringToHGlobalUni(path), Crypt32.CERT_QUERY_CONTENT_FLAG_ALL, Crypt32.CERT_QUERY_FORMAT_FLAG_ALL, 0, out ignored, out contentType, out formatType, ref pIgnored, ref cryptMsg, ref context)) { int error = Marshal.GetLastWin32Error(); Console.WriteLine((new Win32Exception(error)).Message); return; } //expecting '10'; CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED Console.WriteLine("Context Type: " + contentType); //Which implies this is set Console.WriteLine("Crypt Msg: " + cryptMsg.ToInt32()); return; }
要从签名代码中获取证书信息,请使用以下命令:
using System.Security.Cryptography.X509Certificates; X509Certificate basicSigner = X509Certificate.CreateFromSignedFile(filename); X509Certificate2 cert = new X509Certificate2(basicSigner);
那么你可以得到像这样的证书的细节:
Console.WriteLine(cert.IssuerName.Name); Console.WriteLine(cert.SubjectName.Name); // etc
在这里找到解决方案:
http://www.ucosoft.com/how-to-program-to-retrieve-the-authenticode-information.html
这里是缩进:
#define _UNICODE 1 #define UNICODE 1 #include <windows.h> #include <tchar.h> #include <wincrypt.h> #include <Softpub.h> #include <stdio.h> #include <stdlib.h> #pragma comment (lib, "Crypt32") // the Authenticode Signature is encode in PKCS7 #define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING) // Information structure of authenticode sign typedef struct { LPWSTR lpszProgramName; LPWSTR lpszPublisherLink; LPWSTR lpszMoreInfoLink; DWORD cbSerialSize; LPBYTE lpSerialNumber; LPTSTR lpszIssuerName; LPTSTR lpszSubjectName; } SPROG_SIGNATUREINFO, *PSPROG_SIGNATUREINFO; VOID GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo); VOID GetCertificateInfo(HCERTSTORE hStore, PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo); BOOL GetAuthenticodeInformation(LPCTSTR lpszFileName, PSPROG_SIGNATUREINFO pInfo) { HCERTSTORE hStore = NULL; HCRYPTMSG hMsg = NULL; PCMSG_SIGNER_INFO pSignerInfo = NULL; DWORD dwSignerInfo; BOOL bRet = FALSE; __try { // as CryptQueryObject() only accept WCHAR file name, convert first WCHAR wszFileName[MAX_PATH]; #ifdef UNICODE if ( !lstrcpynW( wszFileName, lpszFileName, MAX_PATH)) __leave; #else if ( mbstowcs( wszFileName, lpszFileName, MAX_PATH) == -1) __leave; #endif //Retrieve the Message Handle and Store Handle DWORD dwEncoding, dwContentType, dwFormatType; if ( !CryptQueryObject( CERT_QUERY_OBJECT_FILE, wszFileName, CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, CERT_QUERY_FORMAT_FLAG_BINARY, 0, &dwEncoding, &dwContentType, &dwFormatType, &hStore, &hMsg, NULL)) __leave; //Get the length of SignerInfo if ( !CryptMsgGetParam( hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo)) __leave; // allocate the memory for SignerInfo if ( !(pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc( LPTR, dwSignerInfo))) __leave; // get the SignerInfo if ( !CryptMsgGetParam( hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfo)) __leave; //get the Publisher from SignerInfo GetProgAndPublisherInfo( pSignerInfo, pInfo); //get the Certificate from SignerInfo GetCertificateInfo( hStore, pSignerInfo, pInfo); bRet = TRUE; } __finally { // release the memory if (pSignerInfo != NULL) LocalFree(pSignerInfo); if (hStore != NULL) CertCloseStore(hStore, 0); if (hMsg != NULL) CryptMsgClose(hMsg); } return bRet; } LPWSTR AllocateAndCopyWideString(LPCWSTR inputString) { LPWSTR outputString = NULL; // allocate the memory outputString = (LPWSTR)VirtualAlloc(NULL, (wcslen(inputString) + 1) * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE); // copy if (outputString != NULL) { lstrcpyW(outputString, inputString); } return outputString; } VOID GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo) { PSPC_SP_OPUS_INFO OpusInfo = NULL; DWORD dwData; __try { // query SPC_SP_OPUS_INFO_OBJID OID in Authenticated Attributes for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++) { if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID, pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0) { // get the length of SPC_SP_OPUS_INFO if ( !CryptDecodeObject(ENCODING, SPC_SP_OPUS_INFO_OBJID, pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData, pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData, 0, NULL, &dwData)) __leave; // allocate the memory for SPC_SP_OPUS_INFO if ( !(OpusInfo = (PSPC_SP_OPUS_INFO)LocalAlloc(LPTR, dwData))) __leave; // get SPC_SP_OPUS_INFO structure if ( !CryptDecodeObject(ENCODING, SPC_SP_OPUS_INFO_OBJID, pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData, pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData, 0, OpusInfo, &dwData)) __leave; // copy the Program Name of SPC_SP_OPUS_INFO to the return variable if (OpusInfo->pwszProgramName) { pInfo->lpszProgramName = AllocateAndCopyWideString(OpusInfo->pwszProgramName); } else pInfo->lpszProgramName = NULL; // copy the Publisher Info of SPC_SP_OPUS_INFO to the return variable if (OpusInfo->pPublisherInfo) { switch (OpusInfo->pPublisherInfo->dwLinkChoice) { case SPC_URL_LINK_CHOICE: pInfo->lpszPublisherLink = AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszUrl); break; case SPC_FILE_LINK_CHOICE: pInfo->lpszPublisherLink = AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszFile); break; default: pInfo->lpszPublisherLink = NULL; break; } } else { pInfo->lpszPublisherLink = NULL; } // copy the More Info of SPC_SP_OPUS_INFO to the return variable if (OpusInfo->pMoreInfo) { switch (OpusInfo->pMoreInfo->dwLinkChoice) { case SPC_URL_LINK_CHOICE: pInfo->lpszMoreInfoLink = AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszUrl); break; case SPC_FILE_LINK_CHOICE: pInfo->lpszMoreInfoLink = AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszFile); break; default: pInfo->lpszMoreInfoLink = NULL; break; } } else { pInfo->lpszMoreInfoLink = NULL; } break; // we have got the information, break } } } __finally { if (OpusInfo != NULL) LocalFree(OpusInfo); } } VOID GetCertificateInfo(HCERTSTORE hStore, PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo) { PCCERT_CONTEXT pCertContext = NULL; __try { CERT_INFO CertInfo; DWORD dwData; // query Signer Certificate in Certificate Store CertInfo.Issuer = pSignerInfo->Issuer; CertInfo.SerialNumber = pSignerInfo->SerialNumber; if ( !(pCertContext = CertFindCertificateInStore( hStore, ENCODING, 0, CERT_FIND_SUBJECT_CERT, (PVOID)&CertInfo, NULL))) __leave; dwData = pCertContext->pCertInfo->SerialNumber.cbData; // SPROG_SIGNATUREINFO.cbSerialSize pInfo->cbSerialSize = dwData; // SPROG_SIGNATUREINFO.lpSerialNumber pInfo->lpSerialNumber = (LPBYTE)VirtualAlloc(NULL, dwData, MEM_COMMIT, PAGE_READWRITE); memcpy( pInfo->lpSerialNumber, pCertContext->pCertInfo->SerialNumber.pbData, dwData); // SPROG_SIGNATUREINFO.lpszIssuerName __try { // get the length of Issuer Name if (!(dwData = CertGetNameString( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, NULL, NULL, 0))) __leave; // allocate the memory if ( !(pInfo->lpszIssuerName = (LPTSTR)VirtualAlloc(NULL, dwData * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE))) __leave; // get Issuer Name if (!(CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, NULL, pInfo-> lpszIssuerName, dwData))) __leave; } __finally { } // SPROG_SIGNATUREINFO.lpszSubjectName __try { //get the length of Subject Name if (!(dwData = CertGetNameString( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0))) __leave; // allocate the memory if ( !(pInfo->lpszSubjectName = (LPTSTR)VirtualAlloc(NULL, dwData * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE))) __leave; // get Subject Name if (!(CertGetNameString( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, pInfo->lpszSubjectName, dwData))) __leave; } __finally { } } __finally { if (pCertContext != NULL) CertFreeCertificateContext(pCertContext); } } int _tmain(int argc, TCHAR *argv[]) { if (argc != 2) { _tprintf(_T("Usage: SignedFileInfo \n")); return 0; } else { SPROG_SIGNATUREINFO SignInfo; ZeroMemory(&SignInfo, sizeof(SignInfo)); GetAuthenticodeInformation( argv[1], &SignInfo); wprintf(L"Program Name: %s\n", SignInfo.lpszProgramName); wprintf(L"Publisher Link: %s\n", SignInfo.lpszPublisherLink); wprintf(L"More Info Link: %s\n", SignInfo.lpszMoreInfoLink); { _tprintf(_T("Serial Number: ")); DWORD dwData = SignInfo.cbSerialSize; for (DWORD n = 0; n < dwData; n++) { _tprintf(_T("%02x "), SignInfo.lpSerialNumber[dwData - (n + 1)]); } _tprintf(_T("\n")); } _tprintf(_T("Issuer Name: %s\n"), SignInfo.lpszIssuerName); _tprintf(_T("Subject Name: %s\n"), SignInfo.lpszSubjectName); if ( SignInfo.lpszProgramName) VirtualFree(SignInfo.lpszProgramName, 0, MEM_RELEASE); if ( SignInfo.lpszPublisherLink) VirtualFree(SignInfo.lpszPublisherLink, 0, MEM_RELEASE); if ( SignInfo.lpszMoreInfoLink) VirtualFree(SignInfo.lpszMoreInfoLink, 0, MEM_RELEASE); if ( SignInfo.lpSerialNumber) VirtualFree(SignInfo.lpSerialNumber, 0, MEM_RELEASE); if ( SignInfo.lpszIssuerName) VirtualFree(SignInfo.lpszIssuerName, 0, MEM_RELEASE); if ( SignInfo.lpszSubjectName) VirtualFree(SignInfo.lpszSubjectName, 0, MEM_RELEASE); return 0; } }
这些是我曾经使用过的最糟糕的API
一句警告:比你想象的要糟糕。
至少从引入SHA-256签名(这一直是这种情况?),Authenticode有可能有多个签名。 它们在PKCS-7签名消息中不被编码为多个签名; 相反,它们是未经身份验证的OID_NESTED_SIGNATURE类型的消息属性,每个都包含另一个完整的PKCS-7签名消息。
WinVerifyTrust会告诉你,如果任何签名是有效的,并且来自可信任的证书链,文件是有效的。 但是它不会告诉你哪个签名是有效的。 如果您使用CryptQueryObject读取完整的PKCS-7消息,并且只查看主要签名的证书(如在此处和MSDN中的代码示例中所示),则不一定要查看已验证的证书。 关联的签名可能与可执行文件不匹配,和/或证书可能没有可信的CA链。
如果您使用主签名的详细信息来验证证书是否是您的软件信任的证书,那么很容易出现WinVerifyTrust信任二级签名的情况,但是您的代码正在检查您的预期的主签名证书,而且你还没有注意到来自主证书的签名是无稽之谈。 攻击者可以在不拥有私钥的情况下使用公共证书,并与其他一些发给其他人的代码签名证书一起绕过发行商检查。
从Win8开始,WinVerifyTrust可以选择验证特定的签名,所以您应该能够迭代签名来找到有效的签名, 并且满足您的要求。
如果你必须是Win7兼容,但据我所知最好的你可以管理的是MsiGetFileSignatureInformation。 从实验(至于其他的一切,实际的文档是令人沮丧的),似乎WinVerifyTrust信任一个时,返回可信的证书。 但是,如果没有可信任的签名,无论如何都会返回主签名的证书,所以您仍然必须先使用WinVerifyTrust来检查。
当然,这里也有很多可能的检查时间/使用时间问题。