【windows + 证书验证】 验证数字签名的方法

WinVerifyTrust、CruptQueryObject两种方法的比较

A word of warning: it's worse than you already thought.
警告一句:情况比你想象的还要糟糕。
At least since introducing SHA-256 signing (has this always been the case?), it's possible for Authenticode to have multiple signatures. They're not encoded as multiple signatures in the PKCS-7 signature message; instead, they're unauthenticated message attributes of type OID_NESTED_SIGNATURE, each containing another complete PKCS-7 signature message.

WinVerifyTrust will tell you the file is valid if any of the signatures are valid and come from a trusted certificate chain. However it won't tell you which of the signatures was valid. If you then use CryptQueryObject to read the full PKCS-7 message, and only look at the certificate for the primary signature (as in the code samples here and on MSDN), you're not necessarily looking at a verified certificate. The associated signature might not match the executable, and/or the certificate might not have a trusted CA chain.
WinVerifyTrust可以判断是否有任何一个证书是有效的。 然而它没办法告诉你哪个证书是有效的。 如果是用CruptQueryObject接口,又没有办法得到除了主证书以外的其他证书信息。

If you're using the details of the primary signature to validate that the certificate is one your software trusts, you're vulnerable to a situation where WinVerifyTrust is trusting a secondary signature, but your code is checking the primary signature's certificate is what you expected, and you haven't noticed that the signature from the primary certificate is nonsense. An attacker could use your public certificate without owning its private key, combined with some other code-signing certificate issued to someone else, to bypass a publisher check this way.
如果你通过主证书的详细信息去验证软件是否可信任,在这种场景下容易被攻击:WinVerifyTrust信任了第二个证书,但是你的检查代码又在检查主证书,可能即使主证书并不匹配但是WinVerifyTrust也检查通过了。

From Win8 onwards, WinVerifyTrust can optionally validate specific signatures, so you should be able to iterate the signatures to find one that is valid and one that satisfies your requirements.
win8之后可以指定验证哪个证书是有效的。

If you have to be Win7-compatible, though, as far as I know the best you can manage is MsiGetFileSignatureInformation. From experimentation (as for everything else here, the actual documentation is frustratingly woolly), it seems to return the trusted certificate when WinVerifyTrust trusts one. But if there isn't a trusted signature, it returns the primary signature's certificate anyway, so you still have to use WinVerifyTrust to check that first.

Of course there also plenty of possible time-of-check/time-of-use problems here.

WinVerifyTrust

验证数字签名是否有效,当前的用法解释如下:

//-------------------------------------------------------------------
// Copyright (C) Microsoft.  All rights reserved.
// Example of verifying the embedded signature of a PE file by using 
// the WinVerifyTrust function.

#define _UNICODE 1
#define UNICODE 1

#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <Softpub.h>
#include <wincrypt.h>
#include <wintrust.h>

// Link with the Wintrust.lib file.
#pragma comment (lib, "wintrust")

BOOL VerifyEmbeddedSignature(LPCWSTR pwszSourceFile)
{
    LONG lStatus;
    DWORD dwLastError;

    // Initialize the WINTRUST_FILE_INFO structure.

    // 待验证文件的完整路径
    WINTRUST_FILE_INFO FileData;
    memset(&FileData, 0, sizeof(FileData));
    FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);
    FileData.pcwszFilePath = pwszSourceFile;
    FileData.hFile = NULL;
    FileData.pgKnownSubject = NULL;

    /*
    WVTPolicyGUID specifies the policy to apply on the file
    WINTRUST_ACTION_GENERIC_VERIFY_V2 policy checks:
    
    1) The certificate used to sign the file chains up to a root 
    certificate located in the trusted root certificate store. This 
    implies that the identity of the publisher has been verified by 
    a certification authority.
    
    2) In cases where user interface is displayed (which this example
    does not do), WinVerifyTrust will check for whether the  
    end entity certificate is stored in the trusted publisher store,  
    implying that the user trusts content from this publisher.
    
    3) The end entity certificate has sufficient permission to sign 
    code, as indicated by the presence of a code signing EKU or no 
    EKU.
    */

    GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    WINTRUST_DATA WinTrustData;

    // Initialize the WinVerifyTrust input data structure.

    // Default all fields to 0.
    memset(&WinTrustData, 0, sizeof(WinTrustData));

    WinTrustData.cbStruct = sizeof(WinTrustData);
    
    // Use default code signing EKU.
    WinTrustData.pPolicyCallbackData = NULL;

    // No data to pass to SIP.
    WinTrustData.pSIPClientData = NULL;

    // Disable WVT UI.
    WinTrustData.dwUIChoice = WTD_UI_NONE;

    // No revocation checking.
    WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; 

    // 采用文件方式验证, 其他还有内存等
    // Verify an embedded signature on a file.
    WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;

    // Verify action.
    WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;

    // Verification sets this value.
    WinTrustData.hWVTStateData = NULL;

    // Not used.
    WinTrustData.pwszURLReference = NULL;

    // This is not applicable if there is no UI because it changes 
    // the UI to accommodate running applications instead of 
    // installing applications.
    WinTrustData.dwUIContext = 0;

    // Set pFile.
    WinTrustData.pFile = &FileData;

    // 当前操作是要验证签名,后面还需要调用一次关闭去释放从此次验证操作中得到的放在trustData中的数据
    // PS: 微软为啥老喜欢同一个接口换个flag就变成完全不同的功能,感觉用个FreeWinVerifyTrustedData之类的不好嘛。。(微软:你在教我做事?) -_-
    // WinVerifyTrust verifies signatures as specified by the GUID 
    // and Wintrust_Data.
    lStatus = WinVerifyTrust(
        NULL,
        &WVTPolicyGUID,
        &WinTrustData);

    switch (lStatus) 
    {
        case ERROR_SUCCESS:
            /*
            Signed file:
                - Hash that represents the subject is trusted.

                - Trusted publisher without any verification errors.

                - UI was disabled in dwUIChoice. No publisher or 
                    time stamp chain errors.

                - UI was enabled in dwUIChoice and the user clicked 
                    "Yes" when asked to install and run the signed 
                    subject.
            */
            // 只是验证了签名有效,但是到底是不是预期的签名者进行的签名还不确定, 可以进一步验证
            wprintf_s(L"The file \"%s\" is signed and the signature "
                L"was verified.\n",
                pwszSourceFile);
            break;
        
        case TRUST_E_NOSIGNATURE:
            // The file was not signed or had a signature 
            // that was not valid.

            // Get the reason for no signature.
            dwLastError = GetLastError();
            if (TRUST_E_NOSIGNATURE == dwLastError ||
                    TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError ||
                    TRUST_E_PROVIDER_UNKNOWN == dwLastError) 
            {
                // The file was not signed.
                wprintf_s(L"The file \"%s\" is not signed.\n",
                    pwszSourceFile);
            } 
            else 
            {
                // The signature was not valid or there was an error 
                // opening the file.
                wprintf_s(L"An unknown error occurred trying to "
                    L"verify the signature of the \"%s\" file.\n",
                    pwszSourceFile);
            }

            break;

        case TRUST_E_EXPLICIT_DISTRUST:
            // The hash that represents the subject or the publisher 
            // is not allowed by the admin or user.
            wprintf_s(L"The signature is present, but specifically "
                L"disallowed.\n");
            break;

        case TRUST_E_SUBJECT_NOT_TRUSTED:
            // The user clicked "No" when asked to install and run.
            wprintf_s(L"The signature is present, but not "
                L"trusted.\n");
            break;

        case CRYPT_E_SECURITY_SETTINGS:
            /*
            The hash that represents the subject or the publisher 
            was not explicitly trusted by the admin and the 
            admin policy has disabled user trust. No signature, 
            publisher or time stamp errors.
            */
            wprintf_s(L"CRYPT_E_SECURITY_SETTINGS - The hash "
                L"representing the subject or the publisher wasn't "
                L"explicitly trusted by the admin and admin policy "
                L"has disabled user trust. No signature, publisher "
                L"or timestamp errors.\n");
            break;

        default:
            // The UI was disabled in dwUIChoice or the admin policy 
            // has disabled user trust. lStatus contains the 
            // publisher or time stamp chain error.
            wprintf_s(L"Error is: 0x%x.\n",
                lStatus);
            break;
    }

    // 设置flag为CLOSE, 然后释放掉上面申请出来的内存。。
    // Any hWVTStateData must be released by a call with close.
    WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;

    lStatus = WinVerifyTrust(
        NULL,
        &WVTPolicyGUID,
        &WinTrustData);

    return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
    if(argc > 1)
    {
        VerifyEmbeddedSignature(argv[1]);
    }

    return 0;
}

CryptQueryObject

获取数字签名信息,比如签名者姓名,用于校验签名者是否符合预期

参考

stackoverflow上的这段英文的原文
两个接口使用样例
为什么调用CryptQueryObject接口获取签名时需要传入PKCS #7类型
对这两个接口的使用场景做了简单比较
ms官方使用WinVerifyTrust样例

posted @ 2020-11-25 10:41  mooooonlight  阅读(2303)  评论(0编辑  收藏  举报