Reading multiple signatures from executable file

允我心安 提交于 2019-12-02 23:06:35
Dima

After a lot of digging and trying different things I found that function WinVerifyTrust can read multiple embedded certificates. Disregard the function name, it can be used for many things, it's a universal function.

WinVerifyTrust takes struct WINTRUST_DATA as one of its in/out parameters. Docs says it's IN, but it is also used to return back information.

WINTRUST_DATA has field pSignatureSettings, which is a pointer to another struct, WINTRUST_SIGNATURE_SETTINGS. This stuct has field dwFlags that controls what info will be returned by WinVerifyTrust.

First you call WinVerifyTrust with WINTRUST_SIGNATURE_SETTINGS::dwFlags = WSS_GET_SECONDARY_SIG_COUNT to get back the number of secondary signatures, which is returned in the field WINTRUST_SIGNATURE_SETTINGS::cSecondarySigs. Note that if your file has 2 signatures, cSecondarySigs will be 1.

Then in the loop for (int i = 0; i <= cSecondarySigs; i++) you call WinVerifyTrust with WINTRUST_SIGNATURE_SETTINGS::dwFlags = WSS_VERIFY_SPECIFIC and WINTRUST_SIGNATURE_SETTINGS::dwIndex = i.

After each WinVerifyTrust call you can get certificate info (including countersignatures) from WINTRUST_DATA::hWVTStateData by this call sequence:

WTHelperProvDataFromStateData(hWVTStateData);
WTHelperGetProvSignerFromChain(...);
WTHelperGetProvCertFromChain(...);

I did not dig much into .NET API, but it seems that it can read only first signature. Note that WINTRUST_SIGNATURE_SETTINGS, which seems to be the key to read multiple signatures, was added in Windows 8, so on older OSs you will not be able to read it, at least not with MS API.

Expanding the Dima's answer, I want to provide a sample code which demonstrates how to check all embedded (and nested) leaf (not in the middle of the certificate chain) certificates.

BOOL CheckCertificateIssuer(HANDLE hWVTStateData, const std::set<CString> &stValidIssuers)
{
    CRYPT_PROVIDER_DATA *pCryptProvData = WTHelperProvDataFromStateData(hWVTStateData);
    CRYPT_PROVIDER_SGNR *pSigner = WTHelperGetProvSignerFromChain(pCryptProvData, 0, FALSE, 0);
    CRYPT_PROVIDER_CERT *pCert = WTHelperGetProvCertFromChain(pSigner, 0);

    CString sIssuer;
    int nLength = CertGetNameString(pCert->pCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, NULL, NULL, 0);
    if (!nLength)
    {
        ASSERT(FALSE && "Cannot get the length of the Issuer string");
        return FALSE;
    }

    if (!CertGetNameString(pCert->pCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, NULL, sIssuer.GetBuffer(nLength), nLength))
    {
        ASSERT(FALSE && "Cannot get the Issuer string");
        return FALSE;
    }
    sIssuer.ReleaseBuffer(nLength);
    if (stValidIssuers.find(sIssuer) == stValidIssuers.end())
    {
        ASSERT(FALSE && "Certificate issuer is invalid");
        return FALSE;
    }
    return TRUE;
}
BOOL CheckCertificate(CString filename)
{
    std::set<CString> stValidIssuers;
    stValidIssuers.insert(L"VeriSign Class 3 Code Signing 2010 CA");
    stValidIssuers.insert(L"Symantec Class 3 SHA256 Code Signing CA");

    bool UseStrongSigPolicy = false;

    DWORD Error = ERROR_SUCCESS;
    bool WintrustCalled = false;
    GUID GenericActionId = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    WINTRUST_DATA WintrustData = {};
    WINTRUST_FILE_INFO FileInfo = {};
    WINTRUST_SIGNATURE_SETTINGS SignatureSettings = {};
    CERT_STRONG_SIGN_PARA StrongSigPolicy = {};

    // Setup data structures for calling WinVerifyTrust
    WintrustData.cbStruct = sizeof(WINTRUST_DATA);
    WintrustData.dwStateAction = WTD_STATEACTION_VERIFY;
    WintrustData.dwUIChoice = WTD_UI_NONE;
    WintrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
    WintrustData.dwUnionChoice = WTD_CHOICE_FILE;

    FileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO_);
    FileInfo.pcwszFilePath = filename;
    WintrustData.pFile = &FileInfo;

    //
    // First verify the primary signature (index 0) to determine how many secondary signatures
    // are present. We use WSS_VERIFY_SPECIFIC and dwIndex to do this, also setting
    // WSS_GET_SECONDARY_SIG_COUNT to have the number of secondary signatures returned.
    //
    SignatureSettings.cbStruct = sizeof(WINTRUST_SIGNATURE_SETTINGS);
    SignatureSettings.dwFlags = WSS_GET_SECONDARY_SIG_COUNT | WSS_VERIFY_SPECIFIC;
    SignatureSettings.dwIndex = 0;
    WintrustData.pSignatureSettings = &SignatureSettings;

    if (UseStrongSigPolicy != false)
    {
        StrongSigPolicy.cbSize = sizeof(CERT_STRONG_SIGN_PARA);
        StrongSigPolicy.dwInfoChoice = CERT_STRONG_SIGN_OID_INFO_CHOICE;
        StrongSigPolicy.pszOID = szOID_CERT_STRONG_SIGN_OS_CURRENT;
        WintrustData.pSignatureSettings->pCryptoPolicy = &StrongSigPolicy;
    }
    BOOL bResult = E_NOT_SET;
    TRACE(L"Verifying primary signature... ");
    Error = WinVerifyTrust(NULL, &GenericActionId, &WintrustData);
    WintrustCalled = true;
    if (Error == ERROR_SUCCESS)
    {
        if (CheckCertificateIssuer(WintrustData.hWVTStateData, stValidIssuers))
        {
            if (bResult == E_NOT_SET)
                bResult = TRUE;
        }
        else
        {
            bResult = FALSE;
        }

        TRACE(L"Success!\n");

        TRACE(L"Found %d secondary signatures\n", WintrustData.pSignatureSettings->cSecondarySigs);

        // Now attempt to verify all secondary signatures that were found
        for (DWORD x = 1; x <= WintrustData.pSignatureSettings->cSecondarySigs; x++)
        {
            TRACE(L"Verify secondary signature at index %d... ", x);

            // Need to clear the previous state data from the last call to WinVerifyTrust
            WintrustData.dwStateAction = WTD_STATEACTION_CLOSE;
            Error = WinVerifyTrust(NULL, &GenericActionId, &WintrustData);
            if (Error != ERROR_SUCCESS)
            {
                //No need to call WinVerifyTrust again
                WintrustCalled = false;
                TRACE(L"%s", utils::error::getText(Error));
                ASSERT(FALSE);
                break;
            }

            WintrustData.hWVTStateData = NULL;

            // Caller must reset dwStateAction as it may have been changed during the last call
            WintrustData.dwStateAction = WTD_STATEACTION_VERIFY;
            WintrustData.pSignatureSettings->dwIndex = x;
            Error = WinVerifyTrust(NULL, &GenericActionId, &WintrustData);
            if (Error != ERROR_SUCCESS)
            {
                TRACE(L"%s", utils::error::getText(Error));
                ASSERT(FALSE);
                break;
            }

            if (CheckCertificateIssuer(WintrustData.hWVTStateData, stValidIssuers))
            {
                if (bResult == E_NOT_SET)
                    bResult = TRUE;
            }
            else
            {
                bResult = FALSE;
            }


            TRACE(L"Success!\n");
        }
    }
    else
    {
        TRACE(utils::error::getText(Error));
        ASSERT(FALSE);
    }

    //
    // Caller must call WinVerifyTrust with WTD_STATEACTION_CLOSE to free memory
    // allocate by WinVerifyTrust
    //
    if (WintrustCalled != false)
    {
        WintrustData.dwStateAction = WTD_STATEACTION_CLOSE;
        WinVerifyTrust(NULL, &GenericActionId, &WintrustData);
    }

    return bResult;

}

Looking at

The signature is timestamped: Sat Feb 11 14:03:12 2012

and

Issued to: Microsoft Time-Stamp Service

I'd assume the second signature/certificate is used for time-stamping files. It may well be that MS has two different organizational units, one of which signs the code to acknowledge its integrity and the other one (later) signs the code again, with their own certificate, specifically for secure time-stamping the file.

Certificates can be created and assigned to certain usages. A certificate intended to be used for time-stamping can be marked as such, so there is a chance that signtool, when it encounters a time-stamping certificate, gives an error because by default it expects a certificate for code authenticity/integrity validation and not one for time-stamping.

Maguire

The latest version of SignTool.exe can deal with multiple signatures.

One was would be to use the /ds switch. This lets you select the signature index.

Better yet, here is a great C# example which will read and verify multiple signatures. Code signing an executable twice

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!