2009-07-02 14 views
9

Estoy implementando un asistente de elevación de proceso para Windows. Es un programa que se ejecutará en modo elevado e iniciará otros programas con privilegios de administrador sin mostrar indicaciones UAC adicionales. Por razones de seguridad, quiero asegurarme de que solo se ejecuten los archivos binarios firmados digitalmente con la clave Authenticode de mi empresa.WinVerifyTrust para verificar una firma específica?

La función WinVerifyTrust me lleva a la mitad, pero solo garantiza que un código binario está firmado por alguna clave que forma parte de la cadena de confianza de Microsoft. ¿Existe una forma relativamente simple de realizar la verificación de Authenticode Y asegúrese de que esté firmada por nuestra clave privada?

Respuesta

8

Creo que lo que estás buscando es CryptQueryObject.

Con él, usted debe poder sacar el certificado involucrado de un PE, y hacer cualquier verificación adicional que desee.


A modo de ejemplo, esto lo llevará a un HCRYPTMSG. Desde allí, puede usar CryptMsgGetParam para sacar lo que desee. Tenía la esperanza de hacer algo más "robusto", pero estas API son bastante peludas, ya que requieren una gran cantidad de ramificaciones para manejar todos sus casos de devolución.

Por lo tanto, aquí hay una p/invoke-rific ejemplo de C# (empecé en C, pero eso fue básicamente ilegible):

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

Creo que trataré de desglosar algunos códigos, ya que estoy un poco interesado en la seguridad de Windows. –

+2

+1 Genial, esa es la palabra mágica. Buscando en Google para "CryptQueryObject" y "Authenticode" me consiguió esto: http://support.microsoft.com/kb/323809 - que es justo lo que ordenó el médico. Sin embargo, los invito a agregar su código :) –

+0

Gah, estas son algunas de las API más desagradables con las que he trabajado. No es mi mejor trabajo de ninguna manera. Espero que sea un poco útil para aquellos que vengan a esta pregunta en el futuro. –

7

Para obtener la información del certificado del uso de código firmado el presente:

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

A continuación, puede obtener los detalles del CERT como esto:

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

Esto es agradable y simple, pero si un archivo contiene varias firmas, solo obtiene el primer certificado – Cocowalla

2

encontrado la solución aquí:

http://www.ucosoft.com/how-to-program-to-retrieve-the-authenticode-information.html

Aquí está con muesca:

#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

En realidad, el código es de la Base de conocimiento de Microsoft ([Cómo obtener información de ejecutables firmados de Authenticode] (http://support.microsoft.com)/kb/323809)), ya está correctamente sangrado, y sin anuncios innecesarios. – IInspectable

0

estas son algunas de las APIs más desagradables que he trabajado con

Una palabra de advertencia: es peor de lo que creías

Al menos desde la introducción de la firma SHA-256 (¿ha sido así siempre?), Es posible que Authenticode tenga varias firmas. No están codificados como firmas múltiples en el mensaje de firma PKCS-7; en su lugar, son atributos de mensaje no autenticados de tipo OID_NESTED_SIGNATURE, cada uno con otro mensaje de firma PKCS-7 completo.

WinVerifyTrust le dirá que el archivo es válido si alguna de las firmas es válida y proviene de una cadena de certificados de confianza. Sin embargo, no le dirá cuál de las firmas fue válida. Si luego usa CryptQueryObject para leer el mensaje completo de PKCS-7, y solo mira el certificado para la firma principal (como en los ejemplos de código aquí y en MSDN), no necesariamente está buscando un certificado verificado. La firma asociada puede no coincidir con el ejecutable, y/o el certificado puede no tener una cadena de CA confiable.

Si está utilizando los detalles de la firma principal para validar que el certificado confía en su software, es vulnerable a una situación en la que WinVerifyTrust confía en una firma secundaria, pero su código está verificando el certificado de la firma principal es lo que esperaba, y no ha notado que la firma del certificado primario no tiene sentido. Un atacante podría usar su certificado público sin poseer su clave privada, combinado con algún otro certificado de firma de código otorgado a otra persona, para evitar una verificación de editor de esta manera.

De Windows 8 en adelante, WinVerifyTrust opcionalmente puede validar firmas específicas, por lo que debe ser capaz de repetir las firmas para encontrar uno que sea válida y uno que satisfaga sus necesidades.

Si tiene que ser compatible con Win7, por lo que yo sé, lo mejor que puede administrar es MsiGetFileSignatureInformation. A partir de la experimentación (como en el caso de todo lo demás aquí, la documentación real es muy floja), parece devolver el certificado de confianza cuando WinVerifyTrust confía en uno. Pero si no hay una firma de confianza, devuelve el certificado de la firma principal de todos modos, por lo que todavía tiene que usar WinVerifyTrust para verificarlo primero.

Por supuesto, también hay muchos posibles problemas de tiempo de verificación/tiempo de uso aquí.

Cuestiones relacionadas