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