【问题标题】:Reading multiple signatures from executable file从可执行文件中读取多个签名
【发布时间】:2014-07-22 16:13:48
【问题描述】:

我正在尝试编写从 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,它返回的信息与我的代码相同:第一个证书(带有它的路径)和副署名(带有它的路径)。但也要注意最后的错误。

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 explorer 文件属性对话框可以显示)。

谢谢!

【问题讨论】:

标签: c++ authenticode signatures


【解决方案1】:

经过大量挖掘和尝试不同的事情后,我发现函数WinVerifyTrust 可以读取多个嵌入式证书。别管函数名,它可以用于很多的东西,它是一个通用的函数。

WinVerifyTrust 将 struct WINTRUST_DATA 作为其输入/输出参数之一。文档说它是IN,但它也用于返回信息。

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

在每次 WinVerifyTrust 调用之后,您可以通过此调用序列从 WINTRUST_DATA::hWVTStateData 获取证书信息(包括副署):

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

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

【讨论】:

  • 这正是我一直在寻找的。谢谢!
【解决方案2】:

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

}

【讨论】:

    【解决方案3】:

    看着

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

    Issued to: Microsoft Time-Stamp Service
    

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

    可以创建证书并将其分配给某些用途。可以将用于时间戳的证书标记为这样,因此signtool 在遇到时间戳证书时可能会出错,因为默认情况下它需要一个用于代码真实性/完整性的证书验证,而不是时间戳。

    【讨论】:

    • 不,数字签名选项卡中的第二个证书不仅仅是时间戳。每个签名(总共两个)有 6 个与之关联的证书:3 个证书用于为签名创建路径,3 个用于时间戳,如 signtool 的输出所示。我确实同意两个或多个签名应该是可能的(问题是谁来阅读它们?),但必须在签名时应用时间戳签名,时间戳证书的全部目的是确保它是签名时间.
    • 不太清楚你的意思是什么。我们似乎都明白有两个签名,其中一个用于验证二进制文件的作者身份(“颁发给:Microsoft Code Signing PCA”),第二个用于建立可信时间戳(“颁发给:Microsoft Time-邮票服务”)。这两个签名服务于不同的目的,可能是由 MS 组织内的不同实体创建的。当然,每个签名都带有一个时间戳,但由时间戳权威发布的可信时间戳与“正常”签名具有不同的含义,请参阅 Wikipedia 链接。
    • 在屏幕截图的第二个选项卡上,有 2 个签名,一个是 sha1,另一个是 sha256。 signtool 和我的代码只能读取其中一个,sha1。此签名 (sha1) 有 6 个与之关联的证书,这些证书显示在 signtool 输出中。我可以在文件属性对话框中查看与 sha256 关联的另外 6 个证书(如果您深入使用详细信息按钮,但 signtool 和我的代码都无法显示它们。换句话说:如果您将时间戳签名(副签名)视为单独的签名,此 DLL 共有 4 个签名。但 signtool 只能显示 2 个。
    【解决方案4】:

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

    一种方法是使用 /ds 开关。这使您可以选择签名索引。

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

    【讨论】:

      猜你喜欢
      • 2015-04-30
      • 1970-01-01
      • 2014-01-25
      • 1970-01-01
      • 2021-09-28
      • 2016-04-28
      • 1970-01-01
      • 2014-12-24
      • 2011-11-06
      相关资源
      最近更新 更多