2014-07-22 65 views
11

我正在尝试编写从DLL或EXE读取签名(证书)的代码。大多数DLL或EXE只有一个签名,我的代码正确读取与此签名关联的所有证书。更具体地说,它读取签名证书,它是颁发者(不是根),签署证书(带有时间戳)及其颁发者(不是根)。我在C++和C#中有2个示例程序,它们都返回相同的证书。这是C#代码,C++是长:)从可执行文件读取多个签名

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

100倍但也有有2个签名,如图文件属性的DLL /数字签名,例如C:\ Program Files文件(x86)的\微软SQL服务器\ 80个\ TOOLS \ BINN \ MSVCR71.DLL:

File properties and digital signatures for msvcr71.dll

对于此DLL我的代码读取仅与第一签名相关联的证书。

我也尝试过使用signtool,并且它返回与我的代码相同的信息:第一个cert(与它的路径)和countersignature(与它的路径)。但最后还要注意错误。

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资源管理器文件属性对话框可以显示它)。

谢谢!

+0

你看那些双签名的原因是[微软将弃用](http://social.technet.microsoft.com/wiki/contents/articles/32288.windows-enforcement-of-authenticode-code -signing-and-timestamping.aspx)由于SHA-1的[不足的碰撞抵抗]而产生的SHA-1签名(http://crypto.stackexchange.com/questions/845/what-is-wrong-with-using- SHA1功能于数字签名 - 为什么 - 是 - 一 - 鲁棒哈希functi)。他们今天离开后向兼容。 – ahmd0

回答

0

看着

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

Issued to: Microsoft Time-Stamp Service 

我认为第二个签名/证书用于time-stamping文件。 MS可能有两个不同的组织单位,其中一个签署代码来承认其完整性,另一个(稍后)再次使用自己的证书签署代码,专门用于安全地为文件加时间戳。

证书可以创建并分配给某些用途。意图用于时间戳的证书可以被标记为这样,因此当遇到时间戳证书时,有可能因为默认它期望用于代码真实性/完整性验证的证书而给出错误,没有一个用于时间戳。

+0

不,数字签名选项卡中的第二个证书不仅仅是时间戳。每个签名(总共有两个签名)有6个证书关联:签名的路径为3个证书,signtool的输出显示为3个时间戳。我同意两个或多个签名应该是可能的(问题是谁来阅读他们两个?),但时间戳签名必须在签名时应用,时间戳证书的全部目的是确保签名的时间。 – Dima

+0

不太确定你的观点。我们似乎都明白,有两个签名,其中一个用于验证二进制文件的作者身份(“颁发给:Microsoft代码签名PCA”),另一个用于建立受信任的时间戳(“颁发给:Microsoft Time-邮票服务“)。这两个签名有不同的用途,可能由MS组织内的不同实体创建。当然,每个签名都带有一个时间戳,但由时间戳签发机构发布的可信时间戳与“正常”签名的含义不同,请参阅维基百科链接。 – JimmyB

+0

在sceen shot的第二个标签上有2个signautes,一个是sha1,另一个是sha256。 signtool和我的代码只能读取其中的一个,sha1。这个签名(sha1)有6个与它关联的证书,它们显示在signtool输出中。我可以在文件属性对话框中查看与sha256相关联的另外6个证书(如果您深入了解使用细节按钮,但是signtool和我的代码都不能显示它们)换句话说:如果您将时间戳签名(联署签名)作为单独签名处理,这个DLL总共有4个签名,但signtool只能显示2. – Dima

13

经过大量的挖掘和尝试不同的事情,我发现功能WinVerifyTrust可以读取多个嵌入式证书。忽略功能名称,它可以用于很多的东西,这是一个通用功能。

WinVerifyTrust以结构WINTRUST_DATA作为其输入/输出参数之一。 Docs表示它是IN,但它也用于返回信息。

WINTRUST_DATA有字段pSignatureSettings,这是一个指向另一个结构的指针,WINTRUST_SIGNATURE_SETTINGS。这个stuct有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_SPECIFICWINTRUST_SIGNATURE_SETTINGS::dwIndex = i

此调用序列的每个的WinVerifyTrust电话就可以得到证书信息(包括联署)从WINTRUST_DATA::hWVTStateData后:

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

我并没有太多深入到.NET API,但似乎它可以读取只有第一个签名。请注意,WINTRUST_SIGNATURE_SETTINGS似乎是读取多个签名的关键,在Windows 8中添加了,因此在较旧的操作系统中,您将无法读取它,至少不能使用MS API。

+1

这正是我一直在寻找的。谢谢! –

0

最新版本的SignTool.exe可以处理多个签名。

其中之一就是使用/ ds开关。这使您可以选择签名索引。

更棒的是,这里有一个很棒的C#示例,它将读取和验证多个签名。 Code signing an executable twice

4

扩展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; 

}