2009-07-02 3 views
9

Я реализую вспомогательный помощник процесса для Windows. Это программа, которая будет работать в повышенном режиме и запускать другие программы с правами администратора без отображения дополнительных запросов UAC. Из соображений безопасности я хочу убедиться, что могут быть выполнены только двоичные файлы, подписанные цифровой подписью с ключом Authenticode моей компании.WinVerifyTrust для проверки конкретной подписи?

Функция WinVerifyTrust получает меня на полпути, но это гарантирует только, что двоичный код подписан ключом, который является частью сети доверия Microsoft. Есть ли относительно простой способ выполнить проверку подлинности и убедиться, что он подписан нашим закрытым ключом?

ответ

8

Я считаю, что вы ищете CryptQueryObject.

С его помощью вы сможете вытащить задействованный сертификат из PE и выполнить любые дополнительные проверки.


В качестве примера это приведет вас к HCRYPTMSG. Оттуда вы можете использовать CryptMsgGetParam, чтобы вытащить все, что захотите. Я надеялся сделать что-то более «надежным», но эти API довольно волосатые, поскольку они требуют много разветвлений для обработки всех их случаев возврата.

Итак, вот ар/взывать-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; 
    } 
+0

Я думаю, что я попытаюсь придумать код, так как меня немного интересует безопасность Windows. –

+2

+1 Отлично, это волшебное слово. Googling для «CryptQueryObject» и «Authenticode» получил мне это: http://support.microsoft.com/kb/323809 - это именно то, что доктор заказал. Я приглашаю вас еще добавить код, хотя :) –

+0

Gah, это некоторые из самых неприятных API, с которыми я когда-либо работал. Не моя лучшая работа. Надеюсь, немного полезно для тех, кто придет на этот вопрос в будущем. –

7

Чтобы получить информацию о сертификате из подписанного использования кода следующим образом:

using System.Security.Cryptography.X509Certificates; 
X509Certificate basicSigner = X509Certificate.CreateFromSignedFile(filename); 
X509Certificate2 cert = new X509Certificate2(basicSigner); 

Тогда вы можете получить подробную информацию CERT, как это:

Console.WriteLine(cert.IssuerName.Name); 
Console.WriteLine(cert.SubjectName.Name); 
// etc 
+0

Это красиво и просто, но если файл содержит несколько подписей, он получает только первый сертификат. – Cocowalla

2

нашел решение здесь:

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; 
    } 
} 
+1

Собственно, код из базы знаний Microsoft ([Как получить информацию из подписи под аутентифицированным кодом] (http://support.microsoft.com)./kb/323809)), уже правильно отступом и без лишних объявлений. – IInspectable

0

это одни из самых противных API, я когда-либо работал с

Слово предупреждения: это хуже чем вы уже думали.

По крайней мере, начиная с введения подписи SHA-256 (всегда ли это было?), Authenticode позволяет иметь несколько подписей. Они не кодируются как множественные подписи в сообщении подписи PKCS-7; вместо этого они являются неавторизованными атрибутами сообщения типа OID_NESTED_SIGNATURE, каждый из которых содержит другое полное сообщение подписи PKCS-7.

WinVerifyTrust сообщит вам, что файл действителен, если какая-либо из подписей действительна и принадлежит доверенной цепочке сертификатов. Однако он не скажет вам, какая из подписей была действительной. Если вы затем используете CryptQueryObject для чтения полного сообщения PKCS-7 и посмотрите только на сертификат первичной подписи (как в примерах кода здесь и на MSDN), вы не обязательно смотрите на проверенный сертификат. Соответствующая подпись может не соответствовать исполняемому файлу, и/или сертификат может не иметь доверенную цепочку CA.

Если вы используете детали первичной подписи для проверки того, что сертификат является вашим доверенным программным обеспечением, вы уязвимы в ситуации, когда WinVerifyTrust доверяет вторичную подпись, но ваш код проверяет сертификат первичной подписи это то, что вы ожидали, и вы не заметили, что подпись из первичного сертификата - глупость. Злоумышленник может использовать ваш публичный сертификат, не владея его закрытым ключом, в сочетании с другим сертификатом подписи кода, выпущенным кому-то другим, для обхода этого издателя.

Начиная с версии Win8, WinVerifyTrust может факультативно проверять определенные подписи, поэтому вы должны иметь возможность перебирать подписи, чтобы найти то, что действительно и, которое соответствует вашим требованиям.

Если вам нужно быть совместимым с Win7, хотя, насколько я знаю, лучше всего вы можете управлять MsiGetFileSignatureInformation. Из экспериментов (как и для всего остального здесь, фактическая документация разочаровывает woolly), кажется, возвращает доверенный сертификат, когда WinVerifyTrust доверяет ему. Но если нет надежной подписи, она все равно возвращает сертификат первичной подписи, поэтому вам все равно придется использовать WinVerifyTrust для проверки этого.

Конечно, здесь есть много возможных проблем с проверкой/временем использования.