WinVerifyTrust to check for a specific signature?

16,135

Solution 1

I believe what you're looking for is CryptQueryObject.

With it you should be able to pull the involved certificate out of a PE, and do any additional checks you want.


By way of example, this will get you to a HCRYPTMSG. From there you can use CryptMsgGetParam to pull out whatever you want. I'd hoped to make something more 'robust', but these APIs are pretty hairy insomuch as they require a lot of branching to handle all their return cases.

So, here's a p/invoke-rific c# example (I started in C, but that was basically unreadable):

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

Solution 2

these are some of the nastiest APIs I've ever worked with

A word of warning: it's worse than you already thought.

At least since introducing SHA-256 signing (has this always been the case?), it's possible for Authenticode to have multiple signatures. They're not encoded as multiple signatures in the PKCS-7 signature message; instead, they're unauthenticated message attributes of type OID_NESTED_SIGNATURE, each containing another complete PKCS-7 signature message.

WinVerifyTrust will tell you the file is valid if any of the signatures are valid and come from a trusted certificate chain. However it won't tell you which of the signatures was valid. If you then use CryptQueryObject to read the full PKCS-7 message, and only look at the certificate for the primary signature (as in the code samples here and on MSDN), you're not necessarily looking at a verified certificate. The associated signature might not match the executable, and/or the certificate might not have a trusted CA chain.

If you're using the details of the primary signature to validate that the certificate is one your software trusts, you're vulnerable to a situation where WinVerifyTrust is trusting a secondary signature, but your code is checking the primary signature's certificate is what you expected, and you haven't noticed that the signature from the primary certificate is nonsense. An attacker could use your public certificate without owning its private key, combined with some other code-signing certificate issued to someone else, to bypass a publisher check this way.

From Win8 onwards, WinVerifyTrust can optionally validate specific signatures, so you should be able to iterate the signatures to find one that is valid and one that satisfies your requirements.

If you have to be Win7-compatible, though, as far as I know the best you can manage is MsiGetFileSignatureInformation. From experimentation (as for everything else here, the actual documentation is frustratingly woolly), it seems to return the trusted certificate when WinVerifyTrust trusts one. But if there isn't a trusted signature, it returns the primary signature's certificate anyway, so you still have to use WinVerifyTrust to check that first.

Of course there also plenty of possible time-of-check/time-of-use problems here.

Solution 3

found the solution here:

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

here it is with indentation:

#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;
    }
}
Share:
16,135

Related videos on Youtube

joshk0
Author by

joshk0

I have a day job.

Updated on October 17, 2020

Comments

  • joshk0
    joshk0 about 3 years

    I'm implementing a process elevation helper for Windows. It's a program that will run in elevated mode and launch other programs with administrator privileges without displaying additional UAC prompts. For security reasons, I want to make sure only binaries that are digitally signed with my company's Authenticode key can be executed.

    The WinVerifyTrust function gets me halfway there, but it only ensures that a binary is signed by some key that is part of Microsoft's chain of trust. Is there a relatively simple way to perform the Authenticode verification AND ensure that it is signed by our private key?

  • Kevin Montrose
    Kevin Montrose over 14 years
    I think I'll try and gin up some code, as I'm a little interested in Windows security.
  • Kevin Montrose
    Kevin Montrose over 14 years
    Gah, these are some of the nastiest APIs I've ever worked with. Not my finest work by any means. Hopefully a little helpful for those who come upon this question in the future.
  • IInspectable
    IInspectable almost 9 years
    Actually, the code is from Microsoft's Knowledgebase (How To Get Information from Authenticode Signed Executables), already properly indented, and without needless ads.
  • zett42
    zett42 about 4 years
    "From Win8 onwards" -- Apparently this also works on a patched Win 7.

Related