WinVerifyTrust, чтобы проверить наличие конкретной подписи?

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

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


person joshk0    schedule 02.07.2009    source источник


Ответы (4)


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

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


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

Итак, вот пример c # с p / invoke-rific (я начал с 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;
    }
person Kevin Montrose    schedule 02.07.2009
comment
Думаю, я попробую написать немного кода, так как меня мало интересует безопасность Windows. - person Kevin Montrose; 02.07.2009
comment
+1 Отлично, это волшебное слово. Поиск в Google для CryptQueryObject и Authenticode дал мне следующее: support.microsoft.com/kb/323809 - это именно то, что доктор прописал. Я приглашаю вас все же добавить свой код :) - person joshk0; 02.07.2009
comment
Да уж, это одни из самых мерзких API, с которыми я когда-либо работал. Ни в коем случае не лучшая моя работа. Надеюсь, это немного поможет тем, кто столкнется с этим вопросом в будущем. - person Kevin Montrose; 02.07.2009

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

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
person Community    schedule 25.10.2009
comment
Это красиво и просто, но если файл содержит несколько подписей, он получает только первый сертификат. - person Cocowalla; 11.08.2017

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

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

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

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

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

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

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

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

person bobince    schedule 16.10.2017
comment
Начиная с Win8 - По-видимому, это также работает на исправленной Win 7. - person zett42; 28.08.2019

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

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;
    }
}
person nir    schedule 05.01.2010
comment
Фактически, код взят из базы знаний Microsoft (Как получить информацию из исполняемых файлов, подписанных Authenticode), уже с правильным отступом и без ненужной рекламы. - person IInspectable; 05.01.2015