我正在尝试编写从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:
对于此DLL,我的代码只读取与第一个签名关联的证书。
我也尝试使用signtool,它返回与我的代码相同的信息:first cert(带有它的路径)和counterignature(带有它的路径)。但最后还要注意错误。
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
我有两个问题: - 第二次签名的目的是什么? - 如何阅读(到目前为止只有Windows资源管理器文件属性对话框可以显示它)。
谢谢!
答案 0 :(得分:14)
经过大量挖掘和尝试不同的事情后,我发现函数WinVerifyTrust可以读取多个嵌入式证书。忽略函数名称,它可用于许多事物,它是一个通用函数。
WinVerifyTrust 将struct WINTRUST_DATA
作为其输入/输出参数之一。文档说它是IN
,但它也用于返回信息。
WINTRUST_DATA
包含字段pSignatureSettings
,它是指向另一个结构WINTRUST_SIGNATURE_SETTINGS
的指针。此stuct具有字段dwFlags
,用于控制 WinVerifyTrust 将返回的信息。
首先使用WINTRUST_SIGNATURE_SETTINGS::dwFlags = WSS_GET_SECONDARY_SIG_COUNT
调用 WinVerifyTrust 以获取辅助签名的数量,这将在字段WINTRUST_SIGNATURE_SETTINGS::cSecondarySigs
中返回。请注意,如果您的文件有2个签名,则cSecondarySigs
将为1。
然后在循环for (int i = 0; i <= cSecondarySigs; i++)
中,使用WINTRUST_SIGNATURE_SETTINGS::dwFlags = WSS_VERIFY_SPECIFIC
和WINTRUST_SIGNATURE_SETTINGS::dwIndex = i
调用 WinVerifyTrust 。
在每次 WinVerifyTrust 调用之后,您可以通过此调用序列从WINTRUST_DATA::hWVTStateData
获取证书信息(包括签名):
WTHelperProvDataFromStateData(hWVTStateData);
WTHelperGetProvSignerFromChain(...);
WTHelperGetProvCertFromChain(...);
我没有深入研究.NET API,但它似乎只能读取第一个签名。请注意,在Windows 8中添加了WINTRUST_SIGNATURE_SETTINGS
,它似乎是读取多个签名的关键,因此在较旧的操作系统上,您将无法读取它,至少不能使用MS API。
答案 1 :(得分:5)
扩展Dima的答案,我想提供一个示例代码,演示如何检查所有嵌入(和嵌套)叶子(不在证书链中间)证书。
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;
}
答案 2 :(得分:0)
看着
The signature is timestamped: Sat Feb 11 14:03:12 2012
和
Issued to: Microsoft Time-Stamp Service
我假设第二个签名/证书用于time-stamping个文件。很可能MS有两个不同的组织单位,其中一个签署代码以确认其完整性,另一个(稍后)使用自己的证书再次签署代码,专门用于安全时间戳文件。
可以创建证书并将其分配给某些用法。用于时间戳的证书可以这样标记,因此signtool
在遇到时间戳证书时可能会出错,因为默认情况下它需要证书来验证代码的真实性/完整性验证,而不是时间戳。
答案 3 :(得分:0)
最新版本的SignTool.exe可以处理多个签名。
一个是使用/ ds开关。这使您可以选择签名索引。
更好的是,这是一个很好的C#示例,它将读取和验证多个签名。 Code signing an executable twice