Reading multiple signatures from an executable file

I am trying to write code that reads signatures (certificates) from a DLL or EXE. Most DLLs or EXEs have only one signature, and my code correctly reads all the certificates associated with this signature. In particular, he reads the signature of the certificate, its issuer (non-root), certsigning cert (with a timestamp) and its issuer (non-root). I have 2 example programs in C ++ and C #, they both return the same certificates. 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 the 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 only reads the certificates associated with the first signature.

I also tried using signtool and it returns the same information as my code: first cert (with it path) and countersignature (with its path). But also pay attention to the 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 the second signature - how to read it (so far only the Windows Explorer file properties dialog box can show it).

Thanks!

+11
source share
4 answers

After much searching and searching for various things, I found that the WinVerifyTrust function can read several built-in certificates. Do not pay attention to the name of the function, it can be used for many things, this is a universal function.

WinVerifyTrust takes struct WINTRUST_DATA as one of its I / O parameters. The docs say it is IN , but it is also used to return information.

WINTRUST_DATA has a pSignatureSettings field, which is a pointer to another structure, WINTRUST_SIGNATURE_SETTINGS . This element has a dwFlags field that controls what information will be returned by WinVerifyTrust.

First, you call WinVerifyTrust with WINTRUST_SIGNATURE_SETTINGS::dwFlags = WSS_GET_SECONDARY_SIG_COUNT to return the number of secondary signatures that is returned in the WINTRUST_SIGNATURE_SETTINGS::cSecondarySigs . Please note that if your file has 2 signatures, cSecondarySigs will be 1.

Then, in the for (int i = 0; i <= cSecondarySigs; i++) 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 call to WinVerifyTrust, you can obtain certificate information (including signatures) from WINTRUST_DATA::hWVTStateData using this call sequence:

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

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

+13
source

Having expanded Dima’s answer, I want to provide an example of code that demonstrates how to check all nested (and nested) sheets (not in the middle of 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; } 
+4
source

Looking at

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

and

 Issued to: Microsoft Time-Stamp Service 

I would suggest that a second signature / certificate is used for time type files . It is possible that MS has two different organizational units, one of which signs the code confirming its integrity, and the other (later) signs the code again with its own certificate, in particular, for secure file stamping.

Certificates can be created and tied to specific customs. A certificate intended for use for time stamping may be marked as such, so it is possible that signtool will encounter an error when it encounters a time certificate, because by default it expects a certificate of authenticity / integrity of the validation code, and not one for stamping time.

0
source

The latest version of SignTool.exe can handle multiple signatures.

One of them would be to use the / ds switch. This allows you to select a signature index.

Even better, here is a great C # example that will read and verify multiple signatures. Code that double executes an executable file

0
source

All Articles