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

Я пытаюсь написать код, который читает подписи (сертификаты) из 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 Explorer может показать это).

Спасибо!

13

Решение

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

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

WINTRUST_DATA имеет поле pSignatureSettings, который является указателем на другую структуру, WINTRUST_SIGNATURE_SETTINGS. У этого стукта есть поле 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.

14

Другие решения

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

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;

}
5

Смотря на

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

а также

Issued to: Microsoft Time-Stamp Service

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

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

0

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

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

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

0