65.9K
CodeProject 正在变化。 阅读更多。
Home

使用硬件令牌进行 EV 代码签名

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (7投票s)

2018年12月15日

CPOL

3分钟阅读

viewsIcon

24225

如何使用硬件令牌签名 PE 文件

背景

Secured Globe, Inc.,我们使用 Comodo EV 代码签名证书来签名我们的产品。Comodo 发送一个 SafeNet eToken,其他供应商也一样。类似的产品有 Digicert's

内核模式代码签名

内核驱动程序签名需要内核模式代码签名。这种签名使用交叉证书完成。在 Windows 中,交叉证书允许操作系统内核拥有一个单独受信任的 Microsoft 根机构,并将信任链扩展到多个商业 CA,这些 CA 颁发软件发布者证书 (SPC),这些证书用于代码签名软件,以便在 Windows 上分发、安装和加载。

EV 代码签名阶段

正如 这篇博文中所解释的那样,EV 代码签名需要以下步骤:

  1. 连接到硬件令牌。
  2. 使用根证书(用于 内核模式代码签名
  3. 使用交叉证书(用于内核模式代码签名)
  4. 使用 EV 证书、私钥和根/交叉证书进行代码签名。

证书上下文

证书上下文包含证书的编码和解码表示。cert store 函数返回的证书上下文必须通过调用 CertFreeCertificateContext 函数释放。可以调用 CertDuplicateCertificateContext 函数来制作副本(也必须通过调用 CertFreeCertificateContext 释放)。

typedef struct _CERT_CONTEXT 
{
    DWORD                   dwCertEncodingType;
    BYTE                    *pbCertEncoded;
    DWORD                   cbCertEncoded;
    PCERT_INFO              pCertInfo;
    HCERTSTORE              hCertStore;
} CERT_CONTEXT, *PCERT_CONTEXT;
typedef const CERT_CONTEXT *PCCERT_CONTEXT;

打开令牌

当我们需要访问硬件令牌时,我们可以通过编程方式访问它。

我们需要定义两个常量:

#define SAFENET_TOKEN L"\\\\.\\AKS ifdh 0"
#define EV_PASS "<your private key here"

Safenet eToken 的默认名称为 "\\\\.\\AKS ifdh 0"

第二个参数是你的私钥。

然后我们按如下方式调用 OpenToken

PCCERT_CONTEXT cert = OpenToken(SAFENET_TOKEN, EV_PASS);

返回值是 PCCERT_CONTEXT,这意味着我们可以用它来进行代码签名,就像我们使用常规的代码签名证书一样。

以下是 OpenToken 的代码:

//
PCCERT_CONTEXT OpenToken(const std::wstring& TokenName, const std::string& TokenPin)
{
    const wchar_t DefProviderName[] = L"eToken Base Cryptographic Provider";

    HCRYPTPROV hProv = NULL;
    if (!CryptAcquireContextW(&hProv, TokenName.c_str(), 
        DefProviderName, PROV_RSA_FULL, CRYPT_SILENT))
    {
        DWORD Error = GetLastError();
        wprintf(L"Opening token %ws has failed while calling CryptAcquireContext, 
                error 0x%08X\n", TokenName.c_str(), Error);
        MessageBox(NULL, L"You must insert the Token to your USB port", L"", MB_OK);
        return NULL;
    }
    if (!CryptSetProvParam(hProv, PP_SIGNATURE_PIN, (BYTE*)TokenPin.c_str(), 0))
    {
        DWORD Error = GetLastError();
        wprintf(L"Failed to unlock token %ws! Error 0x%08X\n", TokenName.c_str(), Error);
        CryptReleaseContext(hProv, 0);
        return NULL;
    }
    else
    {
        BOOL bStatus = FALSE;
        DWORD dwErr = 0;
        DWORD dwFlags = CRYPT_FIRST;
        PCCERT_CONTEXT pContextArray[128];
        DWORD dwContextArrayLen = 0;
        HCRYPTKEY hKey = NULL;
        LPBYTE pbCert = NULL;
        DWORD dwCertLen = 0;
        PCCERT_CONTEXT pCertContext = NULL;
        DWORD pKeySpecs[2] = { AT_KEYEXCHANGE, AT_SIGNATURE };
        wprintf(L"Successfully unlocked token %ws\n", TokenName.c_str());
        bStatus = CryptAcquireContext(&hProv,
            TokenName.c_str(),
            DefProviderName,
            PROV_RSA_FULL,
            0);
        if (!bStatus)
        {
            dwErr = GetLastError();
            goto end;
        }

        // convert the container name to unicode

        // Acquire a context on the current container
        if (CryptAcquireContext(&hProv,
            TokenName.c_str(),
            DefProviderName,
            PROV_RSA_FULL,
            0))
        {
            // Loop over all the key specs
            for (int i = 0; i < 2; i++)
            {
                if (CryptGetUserKey(hProv,
                    pKeySpecs[i],
                    &hKey))
                {
                    if (CryptGetKeyParam(hKey,
                        KP_CERTIFICATE,
                        NULL,
                        &dwCertLen,
                        0))
                    {
                        pbCert = (LPBYTE)LocalAlloc(0, dwCertLen);
                        if (!pbCert)
                        {
                            dwErr = GetLastError();
                            goto end;
                        }
                        if (CryptGetKeyParam(hKey,
                            KP_CERTIFICATE,
                            pbCert,
                            &dwCertLen,
                            0))
                        {
                            pCertContext = CertCreateCertificateContext(
                                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                pbCert,
                                dwCertLen);
                            if (pCertContext)
                            {
                                wprintf(L"Signing file\n");
                                pContextArray[dwContextArrayLen++] = pCertContext;
                                

                                return pCertContext;

                            }
                        }
                        LocalFree(pbCert);
                    }
                    CryptDestroyKey(hKey);
                    hKey = NULL;
                }
            }
            CryptReleaseContext(hProv, 0);
            hProv = NULL;
        }
        dwFlags = 0;

    end:
        while (dwContextArrayLen--)
        {
            CertFreeCertificateContext(pContextArray[dwContextArrayLen]);
        }
        if (hKey)
            CryptDestroyKey(hKey);
        if (hProv)
            CryptReleaseContext(hProv, 0);
        return NULL;
    }
}

//

签名文件

我们需要使用的 Windows API 调用是 SignerSignEx2()

在我们通过硬件令牌或直接打开证书(非 EV 代码签名证书)后,我们调用 SignAppxPackage()

这个函数需要使用 LoadLibrary() 和 GetProcAddress() 调用。

    // Type definition for invoking SignerSignEx2 via GetProcAddress
    typedef HRESULT(WINAPI *SignerSignEx2Function)(
        DWORD,
        PSIGNER_SUBJECT_INFO,
        PSIGNER_CERT,
        PSIGNER_SIGNATURE_INFO,
        PSIGNER_PROVIDER_INFO,
        DWORD,
        PCSTR,
        PCWSTR,
        PCRYPT_ATTRIBUTES,
        PVOID,
        PSIGNER_CONTEXT *,
        PVOID,
        PVOID);

    // Load the SignerSignEx2 function from MSSign32.dll
    HMODULE msSignModule = LoadLibraryEx(
        L"MSSign32.dll",
        NULL,
        LOAD_LIBRARY_SEARCH_SYSTEM32);

    if (msSignModule)
    {
        SignerSignEx2Function SignerSignEx2 = reinterpret_cast<SignerSignEx2Function>(
            GetProcAddress(msSignModule, "SignerSignEx2"));
        if (SignerSignEx2)
        {
            hr = SignerSignEx2(
                signerParams.dwFlags,
                signerParams.pSubjectInfo,
                signerParams.pSigningCert,
                signerParams.pSignatureInfo,
                signerParams.pProviderInfo,
                signerParams.dwTimestampFlags,
                signerParams.pszAlgorithmOid,
                signerParams.pwszTimestampURL,
                signerParams.pCryptAttrs,
                signerParams.pSipData,
                signerParams.pSignerContext,
                signerParams.pCryptoPolicy,
                signerParams.pReserved);
        }
        else
        {
            DWORD lastError = GetLastError();
            hr = HRESULT_FROM_WIN32(lastError);
        }

        FreeLibrary(msSignModule);
    }
    else
    {
        DWORD lastError = GetLastError();
        hr = HRESULT_FROM_WIN32(lastError);
    }

    // Free any state used during app package signing
    if (sipClientData.pAppxSipState)
    {
        sipClientData.pAppxSipState->Release();
    }

时间戳

你必须使用时间戳机构对你签名的文件进行时间戳,并连接到该机构。

这是通过安全地通过 URL 检查时间戳服务器以获取当前日期和时间来完成的。 每个签名机构都有自己的时间戳服务器。 时间戳是代码签名过程中的一个额外步骤,但对于 EV 代码签名而言,这是一个要求,它为签名的 PE 增加了一个额外的安全层。

签名前的初步检查

在我们可以签名 PE 之前,我们检查以下内容:

  1. 我们是否有 CertAuthority_ROOT 的有效路径?
  2. 我们是否有 CertAuthority_RSA 的有效路径?
  3. 我们是否有 CROSSCERTPATH 的有效路径?
  4. 我们是否连接到 Internet? (我们需要 Internet 连接才能正确地为签名文件添加时间戳)。

从文件加载证书

假设我们有过程中使用的某个证书的有效路径,我们需要将其加载到内存中。 我编写了以下函数来执行此操作。

std::tuple<DWORD, DWORD, std::string> GetCertificateFromFile
(const wchar_t*                         FileName
    , std::shared_ptr<const CERT_CONTEXT>*   ResultCert)
{
    std::vector<unsigned char> vecAsn1CertBuffer;
    auto tuple_result = ReadFileToVector(FileName, &vecAsn1CertBuffer);

    if (std::get<0>(tuple_result) != 0)
    {
        return tuple_result;
    }

    return GetCertificateFromMemory(vecAsn1CertBuffer, ResultCert);
}

这是 FormMemoryCertStore 函数:

std::tuple<DWORD, DWORD, std::string> FormMemoryCertStore
(const std::vector<std::shared_ptr<const CERT_CONTEXT> >& Certs
    , DWORD                                                      FlagsForAddCertToStore
    , std::shared_ptr<void>*                                      ResultStore)
{
    HCERTSTORE hTmpMemoryStore = ::CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL);

    if (hTmpMemoryStore == NULL)
    {
        return std::make_tuple(E_FAIL, ::GetLastError(), "CertOpenStore(Memory Store)");
    }

    for (unsigned int i = 0; i < Certs.size(); ++i)
    {
        int Result = ::CertAddCertificateContextToStore(hTmpMemoryStore
            , Certs[i].get()
            , FlagsForAddCertToStore
            , NULL);
        if (Result == 0)
        {
            DWORD dwLastError = ::GetLastError();
            ::CertCloseStore(hTmpMemoryStore, 0);
            return std::make_tuple(E_FAIL, dwLastError, "CertAddCertificateContextToStore");
        }
    }

    //All certificates are successfully placed in the repository 
    // - I specify the returned parameter
    *ResultStore = std::shared_ptr<void>
        (hTmpMemoryStore, std::bind(::CertCloseStore, std::placeholders::_1, 0));

    return std::make_tuple(0, 0, "");
}

这是 ReadFileToVector 函数:

std::tuple<DWORD, DWORD, std::string> ReadFileToVector
(const std::wstring&            FileName
    , std::vector<unsigned char>*    ResultData)
{
    HANDLE hFile = ::CreateFile
                   (FileName.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        return std::make_tuple(E_FAIL, ::GetLastError(), 
                              "CreateFile(GENERIC_READ, OPEN_EXISTING,)");
    }

    std::shared_ptr<void> sptrTemp(hFile, ::CloseHandle);

    DWORD dwDataLen = ::GetFileSize(hFile, NULL);

    if (dwDataLen == INVALID_FILE_SIZE)
    {
        return std::make_tuple(E_FAIL, ::GetLastError(), "GetFileSize");
    }

    ResultData->resize(dwDataLen);

    DWORD dwNumberOfReadData = 0;
    DWORD Result = ::ReadFile(hFile, &(*ResultData)[0], 
             static_cast<DWORD>(ResultData->size()), &dwNumberOfReadData, NULL);

    if (Result == 0)
    {
        return std::make_tuple(E_FAIL, ::GetLastError(), "ReadFile");
    }

    return std::make_tuple(0, 0, "");
}

从内存加载证书

在我们从文件加载证书后,我们需要将其加载到内存中。 这是使用以下函数完成的:

std::tuple<DWORD, DWORD, std::string> GetCertificateFromMemory
(const std::vector<unsigned char>&      CertData
    , std::shared_ptr<const CERT_CONTEXT>*   ResultCert)
{
    const CERT_CONTEXT* crtResultCert = ::CertCreateCertificateContext
    (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING
        , &CertData[0]
        , static_cast<DWORD>(CertData.size()));
    if (crtResultCert == NULL)
    {
        return std::make_tuple(E_FAIL
            , ::GetLastError()
            , "CertCreateCertificateContext");
    }

    *ResultCert = std::shared_ptr<const CERT_CONTEXT>(crtResultCert
        , ::CertFreeCertificateContext);
    return std::make_tuple(0, 0, "");
}

如本文所示,嵌入在硬件令牌中的证书已从令牌加载到内存中。 现在,这就是我们访问它的方式:

std::vector<unsigned char> dataCertEV(signingCertContext->pbCertEncoded,
        signingCertContext->pbCertEncoded + signingCertContext->cbCertEncoded);

SignAppxPackage 函数

现在,我们准备好使用 SignAppxPackge() 函数,如下所示:

HRESULT SignAppxPackage(
    _In_ PCCERT_CONTEXT signingCertContext,
    _In_ LPCWSTR packageFilePath)
{
    HRESULT hr = S_OK;
    if (PathFileExists(CertAuthority_ROOT))
    {
        wprintf(L"Cross Certificate '%s' was found\n", CertAuthority_ROOT);
    }
    else
    {
        wprintf(L"Error: Cross Certificate '%s' was not found\n", CertAuthority_ROOT);
        return 3;
    }
    DWORD dwReturnedFlag;
    if (InternetGetConnectedState(&dwReturnedFlag,0) == NULL) 
    {
        wprintf(L"Certificate can't be dated with no Internet connection\n");
        return 1;
    }
    if (PathFileExists(CertAuthority_RSA))
    {
        wprintf(L"Cross Certificate '%s' was found\n", CertAuthority_RSA);
    }
    else
    {
        wprintf(L"Error: Cross Certificate '%s' was not found\n", CertAuthority_RSA);
        return 2;
    }
    if (PathFileExists(CROSSCERTPATH))
    {
        wprintf(L"Microsoft Cross Certificate '%s' was found\n", CROSSCERTPATH);
    }
    else
    {
        wprintf(L"Error: Microsoft Cross Certificate '%s' was not found\n", CROSSCERTPATH);
        return 3;
    }
    // Initialize the parameters for SignerSignEx2
    DWORD signerIndex = 0;

    SIGNER_FILE_INFO fileInfo = {};
    fileInfo.cbSize = sizeof(SIGNER_FILE_INFO);
    fileInfo.pwszFileName = packageFilePath;

    SIGNER_SUBJECT_INFO subjectInfo = {};
    subjectInfo.cbSize = sizeof(SIGNER_SUBJECT_INFO);
    subjectInfo.pdwIndex = &signerIndex;
    subjectInfo.dwSubjectChoice = SIGNER_SUBJECT_FILE;
    subjectInfo.pSignerFileInfo = &fileInfo;

    SIGNER_CERT_STORE_INFO certStoreInfo = {};
    certStoreInfo.cbSize = sizeof(SIGNER_CERT_STORE_INFO);
    certStoreInfo.dwCertPolicy = 
                  SIGNER_CERT_POLICY_STORE;// SIGNER_CERT_POLICY_CHAIN_NO_ROOT;
    certStoreInfo.pSigningCert = signingCertContext;

    // Issuer: 'CertAuthority RSA Certification Authority'
    // Subject 'CertAuthority RSA Extended Validation Code Signing CA'
    auto fileCertAuthorityRsaEVCA = CertAuthority_RSA;
    std::shared_ptr<const CERT_CONTEXT> certCertAuthorityRsaEVCA;
    auto tuple_result = GetCertificateFromFile
                        (fileCertAuthorityRsaEVCA, &certCertAuthorityRsaEVCA);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) 
        << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    std::shared_ptr<const CERT_CONTEXT> certCertEV;
    std::vector<unsigned char> dataCertEV(signingCertContext->pbCertEncoded,
        signingCertContext->pbCertEncoded + signingCertContext->cbCertEncoded);
    tuple_result = GetCertificateFromMemory(dataCertEV, &certCertEV);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " 
        << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    // Issuer:  'Microsoft Code Verification Root'
    // Subject: 'CertAuthority RSA Certification Authority'
    auto fileCertCross = CertAuthority_ROOT;
    std::shared_ptr<const CERT_CONTEXT> certCertCross;
    tuple_result = GetCertificateFromFile(fileCertCross, &certCertCross);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " 
        << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    //certificate 1 Issuer  : '<Certificate Provider> RSA Certification Authority'
    //              Subject : '<Certificate Provider> Extended Validation Code Signing CA'
    //
    //certificate 2 Issuer  : '<Certificate Provider> Extended Validation Code Signing CA'
    //              Subject : '<Your company / entity name>'
    //
    //certificate 3 Issuer  : 'Microsoft Code Verification Root'
    //              Subject : '<Certificate Provider> Certification Authority'

    std::vector<std::shared_ptr<const CERT_CONTEXT> > certs;
    certs.push_back(certCertAuthorityRsaEVCA);
    certs.push_back(certCertEV);
    certs.push_back(certCertCross);

    std::shared_ptr<void> resultStore;
    tuple_result = FormMemoryCertStore(certs, CERT_STORE_ADD_NEW, &resultStore);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " 
        << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    certStoreInfo.hCertStore = resultStore.get();
    //--------------------------------------------------------------------

    SIGNER_CERT cert = {};
    cert.cbSize = sizeof(SIGNER_CERT);
    cert.dwCertChoice = SIGNER_CERT_STORE;
    cert.pCertStoreInfo = &certStoreInfo;

    // The algidHash of the signature to be created must match the
    // hash algorithm used to create the app package
    SIGNER_SIGNATURE_INFO signatureInfo = {};
    signatureInfo.cbSize = sizeof(SIGNER_SIGNATURE_INFO);
    signatureInfo.algidHash = CALG_SHA_256;
    signatureInfo.dwAttrChoice = SIGNER_NO_ATTR;

    SIGNER_SIGN_EX2_PARAMS signerParams = {};
    signerParams.pSubjectInfo = &subjectInfo;
    signerParams.pSigningCert = &cert;
    signerParams.pSignatureInfo = &signatureInfo;
    signerParams.dwTimestampFlags = SIGNER_TIMESTAMP_RFC3161;
    signerParams.pszAlgorithmOid = szOID_NIST_sha256;
    //signerParams.dwTimestampFlags = SIGNER_TIMESTAMP_AUTHENTICODE;
    //signerParams.pszAlgorithmOid = NULL;
    signerParams.pwszTimestampURL = TIMESTAMPURL;

    APPX_SIP_CLIENT_DATA sipClientData = {};
    sipClientData.pSignerParams = &signerParams;
    signerParams.pSipData = &sipClientData;

    // Type definition for invoking SignerSignEx2 via GetProcAddress
    typedef HRESULT(WINAPI *SignerSignEx2Function)(
        DWORD,
        PSIGNER_SUBJECT_INFO,
        PSIGNER_CERT,
        PSIGNER_SIGNATURE_INFO,
        PSIGNER_PROVIDER_INFO,
        DWORD,
        PCSTR,
        PCWSTR,
        PCRYPT_ATTRIBUTES,
        PVOID,
        PSIGNER_CONTEXT *,
        PVOID,
        PVOID);

    // Load the SignerSignEx2 function from MSSign32.dll
    HMODULE msSignModule = LoadLibraryEx(
        L"MSSign32.dll",
        NULL,
        LOAD_LIBRARY_SEARCH_SYSTEM32);

    if (msSignModule)
    {
        SignerSignEx2Function SignerSignEx2 = reinterpret_cast<SignerSignEx2Function>(
            GetProcAddress(msSignModule, "SignerSignEx2"));
        if (SignerSignEx2)
        {
            hr = SignerSignEx2(
                signerParams.dwFlags,
                signerParams.pSubjectInfo,
                signerParams.pSigningCert,
                signerParams.pSignatureInfo,
                signerParams.pProviderInfo,
                signerParams.dwTimestampFlags,
                signerParams.pszAlgorithmOid,
                signerParams.pwszTimestampURL,
                signerParams.pCryptAttrs,
                signerParams.pSipData,
                signerParams.pSignerContext,
                signerParams.pCryptoPolicy,
                signerParams.pReserved);
        }
        else
        {
            DWORD lastError = GetLastError();
            hr = HRESULT_FROM_WIN32(lastError);
        }

        FreeLibrary(msSignModule);
    }
    else
    {
        DWORD lastError = GetLastError();
        hr = HRESULT_FROM_WIN32(lastError);
    }

    // Free any state used during app package signing
    if (sipClientData.pAppxSipState)
    {
        sipClientData.pAppxSipState->Release();
    }

    return hr;
}

因此,如果 FILETOSIGN 包含我们想要签名的 PE 名称,我们的整个代码签名过程包括以下函数调用:

PCCERT_CONTEXT cert = OpenToken(SAFENET_TOKEN, EV_PASS);
HRESULT hr = SignAppxPackage(cert, FILETOSIGN);

将内核驱动程序提交给 Microsoft 实验室

内核驱动程序需要按照本文所述进行 EV 代码签名和交叉签名。 然后,你需要将驱动程序的文件打包到一个 cab 文件中并对其进行签名。 这是我写的一篇 文章,解释了如何做到这一点。

关注点

历史

  • 2018 年 12 月 15 日:初始版本
© . All rights reserved.