Reading multiple signatures from executable file
Asked Answered
S

4

16

I am trying to write code that reads signatures (certificates) from DLLs or and EXEs. Most DLLs or EXEs have only one signature, and my code reads all certificates associated with this signature correctly. More specifically it reads signing cert, it's issuer (not root), countersigning cert (with timestamp) and its issuer (not root). I have 2 sample programs in C++ and C#, they both return the same certs. This is C# code, C++ is 100 times longer :)

static void Main(string[] args)
{
    X509Certificate2Collection collection = new X509Certificate2Collection();
    collection.Import(args[0]);
}

But there are DLLs that have 2 signatures, as shown in file properties / Digital Signatures, for example C:\Program Files (x86)\Microsoft SQL Server\80\Tools\Binn\msvcr71.dll:

File properties and digital signatures for msvcr71.dll

For this DLL my code reads only certificates associated with first signature.

I also tried to use signtool, and it returns the same info as my code: first cert (with it's path), and countersignature (with its path). But also note error at the end.

C:\Windows>signtool verify /d /v "C:\Program Files (x86)\Microsoft SQL Server\80\Tools\Binn\msvcr71.dll"

Verifying: C:\Program Files (x86)\Microsoft SQL Server\80\Tools\Binn\msvcr71.dll
Signature Index: 0 (Primary Signature)
Hash of file (sha1): 33BBCCF6326276B413A1ECED1BF7842A6D1DDA07

Signing Certificate Chain:
Issued to: Microsoft Root Certificate Authority
Issued by: Microsoft Root Certificate Authority
Expires:   Sun May 09 19:28:13 2021
SHA1 hash: CDD4EEAE6000AC7F40C3802C171E30148030C072

    Issued to: Microsoft Code Signing PCA
    Issued by: Microsoft Root Certificate Authority
    Expires:   Wed Jan 25 19:32:32 2017
    SHA1 hash: FDD1314ED3268A95E198603BA8316FA63CBCD82D

        Issued to: Microsoft Corporation
        Issued by: Microsoft Code Signing PCA
        Expires:   Fri Feb 01 18:49:17 2013
        SHA1 hash: 8849D1C0F147A3C8327B4038783AEC3E06C76F5B

The signature is timestamped: Sat Feb 11 14:03:12 2012
Timestamp Verified by:
Issued to: Microsoft Root Certificate Authority
Issued by: Microsoft Root Certificate Authority
Expires:   Sun May 09 19:28:13 2021
SHA1 hash: CDD4EEAE6000AC7F40C3802C171E30148030C072

    Issued to: Microsoft Time-Stamp PCA
    Issued by: Microsoft Root Certificate Authority
    Expires:   Sat Apr 03 09:03:09 2021
    SHA1 hash: 375FCB825C3DC3752A02E34EB70993B4997191EF

        Issued to: Microsoft Time-Stamp Service
        Issued by: Microsoft Time-Stamp PCA
        Expires:   Thu Oct 25 16:42:17 2012
        SHA1 hash: FC33104FAE31FB538749D5F2D17FA0ECB819EAE5

SignTool Error: The signing certificate is not valid for the requested usage.
    This error sometimes means that you are using the wrong verification
    policy. Consider using the /pa option.

Number of files successfully Verified: 0
Number of warnings: 0
Number of errors: 1

I have 2 questions: - what is the purpose of second signature - how to read it (so far only windows explorer file property dialog can show it).

Thank you!

Stuccowork answered 22/7, 2014 at 16:13 Comment(2)
The reason you see those dual signatures is because Microsoft is deprecating SHA-1 signatures due to SHA-1's inadequate collision resistance. They are left today for backward compatibility.Cablegram
Linking related answer: https://mcmap.net/q/749207/-winverifytrust-to-check-for-a-specific-signature/7571258Kavanagh
S
20

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.

Stuccowork answered 28/7, 2014 at 19:22 Comment(1)
This exactly what I've been looking for. Thank you!Positronium
F
7

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;

}
Forsook answered 4/2, 2016 at 15:0 Comment(0)
K
0

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.

Kinship answered 22/7, 2014 at 16:26 Comment(3)
Nope, the second cert in the Digital Signature tab is not just timestamp. Each signature (from total of two) has 6 certificates associated with it: 3 certs to make path for the signature, and 3 for timestamp as shown in the output of signtool. I do agree that two or more signatures should be possible (the question is who to read both of them?), but timestamp signature MUST be applied at the time of signing, the whole purpose of timestamp cert is to ensure it's time of the signing.Stuccowork
Not quite sure what your point is. We both seem to understand that there are two signatures, of which one serves to validate the authorship of the binary ("Issued to: Microsoft Code Signing PCA") and the second one to establish a trusted timestamp ("Issued to: Microsoft Time-Stamp Service"). The two signatures serve different purposes and are probably created by different entities within the MS organisation. Of course, every signature bears a timestamp, but a trusted timestamp issued by a timestamping authority has a different meaning than a 'normal' signature, see the Wikipedia link.Kinship
On the second tab in sceen shot there are 2 signautes, one is sha1, another is sha256. signtool and my code can read only one of them, sha1. This signature (sha1) has 6 certificates associated with it, which are displayed in signtool output. I can view another 6 certificates associated with sha256 in the file property dialog (if you drill down into using Details button, but neither signtool nor my code can show them. In other words: if you treat timestamp signatures (countersignatures) as separate signatures, this DLL has total of 4 signatures. But signtool can show only 2.Stuccowork
K
0

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

Knighten answered 6/1, 2016 at 18:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.