Чтение нескольких подписей из исполняемого файла

Я пытаюсь написать код, который считывает подписи (сертификаты) из библиотек DLL или EXE. Большинство DLL или EXE имеют только одну подпись, и мой код правильно считывает все сертификаты, связанные с этой подписью. В частности, он читает подписывающий сертификат, его эмитент (не root), подписывающий сертификат (с меткой времени) и его эмитент (не root). У меня есть 2 примера программ на C ++ и C #, они обе возвращают одни и те же сертификаты. Это код C #, C ++ в 100 раз длиннее :)

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

Но есть библиотеки DLL, которые имеют 2 подписи, как показано в свойствах файла / цифровых подписях, например C: \ Program Files (x86) \ Microsoft SQL Server \ 80 \ Tools \ Binn \ msvcr71.dll:

Свойства файла и цифровые подписи для msvcr71.dll

Для этой DLL мой код читает только сертификаты, связанные с первой подписью.

Я также пытался использовать signtool, и он возвращает ту же информацию, что и мой код: первый сертификат (с его путем) и контрподпись (с его путем). Но также обратите внимание на ошибку в конце.

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

У меня 2 вопроса: - какова цель второй подписи - как ее читать (пока только диалоговое окно свойств файла проводника Windows может отображать это).

Спасибо!


person Dima    schedule 22.07.2014    source источник
comment
Причина, по которой вы видите эти двойные подписи, заключается в том, что Microsoft прекращает поддержку SHA-1 подписей из-за недостаточная устойчивость к коллизиям. Они оставлены сегодня для обратной совместимости.   -  person ahmd0    schedule 29.04.2016
comment
Связанный ответ: stackoverflow.com/q/1072540/7571258   -  person zett42    schedule 28.08.2019


Ответы (4)


После долгих поисков и проб разных вещей я обнаружил, что функция WinVerifyTrust может читать несколько встроенных сертификатов. Не обращайте внимания на имя функции, его можно использовать для многих вещей, это универсальная функция.

WinVerifyTrust принимает struct _ 1_ в качестве одного из параметров входа / выхода. Документы говорят, что это IN, но он также используется для возврата информации.

WINTRUST_DATA имеет поле pSignatureSettings, которое является указателем на другую структуру, _ 5_. В этом элементе есть поле dwFlags, которое определяет, какая информация будет возвращена WinVerifyTrust.

Сначала вы вызываете WinVerifyTrust с WINTRUST_SIGNATURE_SETTINGS::dwFlags = WSS_GET_SECONDARY_SIG_COUNT, чтобы вернуть количество вторичных подписей, которое возвращается в поле WINTRUST_SIGNATURE_SETTINGS::cSecondarySigs. Обратите внимание: если у вашего файла 2 подписи, cSecondarySigs будет 1.

Затем в цикле for (int i = 0; i <= cSecondarySigs; i++) вы вызываете WinVerifyTrust с WINTRUST_SIGNATURE_SETTINGS::dwFlags = WSS_VERIFY_SPECIFIC и WINTRUST_SIGNATURE_SETTINGS::dwIndex = i.

После каждого вызова WinVerifyTrust вы можете получить информацию о сертификате (включая встречные подписи) от WINTRUST_DATA::hWVTStateData с помощью этой последовательности вызовов:

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

В .NET API особо не копался, но вроде может читать только первую подпись. Обратите внимание, что WINTRUST_SIGNATURE_SETTINGS, который, кажется, является ключом для чтения нескольких подписей, был добавлен в Windows 8, поэтому в более старых ОС вы не сможете его прочитать, по крайней мере, с помощью MS API.

person Dima    schedule 28.07.2014
comment
Это именно то, что я искал. Спасибо! - person Martin Costello; 04.06.2015

Расширяя ответ Димы, я хочу предоставить образец кода, который демонстрирует, как проверять все встроенные (и вложенные) листовые (не в середине цепочки сертификатов) сертификаты.

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;

}
person izogfif    schedule 04.02.2016

Смотря на

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

и

Issued to: Microsoft Time-Stamp Service

Я предполагаю, что вторая подпись / сертификат используется для отметки времени файлы. Вполне может быть, что у MS есть два разных организационных подразделения, одно из которых подписывает код, чтобы подтвердить его целостность, а другое (позднее) подписывает код снова, своим собственным сертификатом, специально для безопасной отметки времени файла.

Сертификаты могут быть созданы и назначены для определенных видов использования. Сертификат, предназначенный для использования для отметки времени, может быть помечен как таковой, поэтому существует вероятность, что signtool, когда он встречает сертификат с отметкой времени, выдаст ошибку, потому что по умолчанию он ожидает сертификат для проверки подлинности / целостности кода и не для отметки времени.

person JimmyB    schedule 22.07.2014
comment
Нет, второй сертификат на вкладке «Цифровая подпись» - это не просто временная метка. Каждая подпись (из двух) имеет 6 связанных с ней сертификатов: 3 сертификата для создания пути для подписи и 3 для метки времени, как показано в выходных данных signtool. Я согласен с тем, что две или более подписи должны быть возможны (вопрос в том, кто их прочитает?), Но подпись с меткой времени ДОЛЖНА применяться во время подписания, вся цель сертификата с меткой времени состоит в том, чтобы гарантировать, что пришло время подписи . - person Dima; 22.07.2014
comment
Не совсем уверен, о чем вы говорите. Мы оба, кажется, понимаем, что существует две подписи, одна из которых служит для проверки авторства двоичного файла (выдана: Microsoft Code Signing PCA), а вторая - для установления надежной метки времени (выдана: Microsoft Time-Stamp Service) . Две подписи служат разным целям и, вероятно, создаются разными объектами в организации MS. Конечно, каждая подпись имеет отметку времени, но доверенная отметка времени, выданная органом отметки времени, имеет другое значение, чем «нормальная» подпись, см. Ссылку в Википедии. - person JimmyB; 23.07.2014
comment
На второй вкладке в скриншоте есть 2 сигнала, один - sha1, другой - sha256. signtool, и мой код может читать только один из них, sha1. Эта подпись (sha1) имеет 6 связанных с ней сертификатов, которые отображаются в выводе signtool. Я могу просмотреть еще 6 сертификатов, связанных с sha256, в диалоговом окне свойств файла (если вы перейдете к кнопке Details, но ни signtool, ни мой код не могут их показать. Другими словами: если вы обрабатываете подписи с метками времени (контрподписи) как отдельные подписи, эта DLL имеет всего 4 подписи, но signtool может отображать только 2. - person Dima; 23.07.2014

Последняя версия SignTool.exe может работать с несколькими подписями.

Один из них - использовать переключатель / ds. Это позволяет вам выбрать индекс подписи.

А еще лучше, вот отличный пример C #, который будет читать и проверять несколько подписей. Код, дважды подписывающий исполняемый файл

person Maguire    schedule 06.01.2016