使用WinCrypt或CNG验证签名文件(PKCS7)

时间:2019-01-16 17:42:13

标签: openssl sign asn.1 cng wincrypt

我需要使用Windows crypto API方法验证签名的JAR文件。我对加密和签名事项只有基本的了解。我对那些加密API(WinCrypt,Bcrypt,Ncrypt)还是陌生的。验证文件哈希值不是问题,但是签名部分阻止了我。

借助OpenSSL,PKCS7 RFC(https://tools.ietf.org/html/rfc2315)和其他各种资源,我能够弄清JAR中包含的META-INF / LOCALSIG.DSA的实际文件内容。但是经过两周的挖掘,反复试验后,我仍然陷于困境,不知道还可以尝试什么。

OpenSSL具有不错的命令openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -noverify,它确实可以完成我想做的事情。不幸的是,我在Windows API中找不到如此高级的命令。

我已经尝试使用所有三个API的VerifySignature函数家族,但是我需要为这些API提供公钥,并且我对使用任何ImportKey函数都不满意。因此,我尝试使用CryptDecodeObjectEx手动剖析ASN1格式,以便将各个部分作为BLOB传递给API函数。虽然我在此方面取得了一些成功,但由于无法弄清楚如何解析集合而再次陷入困境。我不想从头开始编写自己的ASN1解析器...

那么,如何将PKCS7签名文件与Windows加密API一起使用?

我猜想使用OpenSSL可能会更容易,但是我不得不说服我的雇主仅出于此目的将OpenSSL添加到我们的代码库中...

更新:

LOCALSIG.DSA文件包含签名者证书和LOCALSIG.SF文件的签名哈希。可以使用openssl pkcs7 -inform der -print_certs -text -in LOCALSIG.DSAopenssl cms -cmsout -inform DER -print -in LOCALSIG.DSA进行验证。

该证书由我们公司和在证书存储区中自签名。我可能需要提供整个信任链。这就是为什么我将-noverify选项添加到openssl smime -verify的原因。

实际上,有两种情况具有不同的证书(内部和外部发行版),一种使用DSA(sig文件包含一个证书),另一种使用RSA(sig文件包含三个证书)。这意味着我无法硬编码要使用的证书或方法。我需要从提供的文件中提取该信息。我该怎么办?

2 个答案:

答案 0 :(得分:2)

据我所知,您需要验证签名分离的PKC7。为此,可以使用功能CryptVerifyDetachedMessageSignature

示例代码:

CRYPT_VERIFY_MESSAGE_PARA vparam = { 0 };
    vparam.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
    vparam.dwMsgAndCertEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
    vparam.pfnGetSignerCertificate = nullptr;
    vparam.pvGetArg = nullptr;


    /* read your files somehow */
    std::vector<char> sig;
    if (!ReadFile("LOCALSIG.DSA", &sig))
    {
        std::cout << "Failed to read file" << std::endl;
        return;
    }

    std::vector<char> mes;
    if (!ReadFile("LOCALSIG.SF", &mes))
    {
        std::cout << "Failed to read file" << std::endl;
        return;
    }

    /* CryptVerifyDetachedMessageSignature requires array of messages to verify */
    const BYTE* marray[] = {
        reinterpret_cast<BYTE*>(mes.data())
    };
    DWORD marray_len[] = {
        static_cast<DWORD>(mes.size())
    };

    if (!CryptVerifyDetachedMessageSignature(vparam,
        0, 
        reinterpret_cast<BYTE*>(sig.data()),
        static_cast<DWORD>(sig.size()), 
        1, /* number of messages in marray */
        marray,
        marray_len,
        nullptr))
    {
        std::cout << "Failed to verify signature, error: " << GetLastError() << std::endl;
    }
    else
    {
        std::cout << "Verify success, signature valid" << std::endl;
    }

更新
要验证证书链,您需要使用CertGetCertificateChain

示例代码:

PCCERT_CHAIN_CONTEXT pChain = nullptr;
CERT_CHAIN_PARA chainPara = {0};
HCERTSTORE hStore = nullptr;
/* you sig file */
DATA_BLOB db = {
    static_cast<DWORD>(sig.size()), reinterpret_cast<BYTE *>(sig.data())};

/* you can open your sig file as certificate store */
hStore = CertOpenStore(CERT_STORE_PROV_PKCS7, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, 0, reinterpret_cast<BYTE *>(&db));
if (!hStore)
{
    goto Exit;
}

chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
chainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
chainPara.RequestedUsage.Usage.cUsageIdentifier = 0;

if (!CertGetCertificateChain(NULL,  /* use default chain engine */
                             pCert, /*  pCert - pointer to signer cert structure (the parameter that was obtained in the previous step) */
                             NULL,
                             hStore, /* point to additional store where need to search for certificates to build chain */
                             &chainPara,
                             CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
                             NULL,
                             &pChain))
{
    std::cout << "failed to build chain: " << GetLastError();
    goto Exit;
}

if (pChain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
{
    std::cout << "certificate valid";
    goto Exit;
}
if (pChain->TrustStatus.dwErrorStatus & CERT_TRUST_REVOCATION_STATUS_UNKNOWN)
{
    std::cout << "certificate revocation status unknown";
}
/* you need to place root certificate to the Trusted Root Store */
if (pChain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_UNTRUSTED_ROOT)
{
    std::cout << "untrusted CA";
}
/* and so on */

Exit : 
if (pCert)
{
    CertFreeCertificateContext(pCert);
}
if (pChain)
{
    CertFreeCertificateChain(pChain);
}
if (hStore)
{
    CertCloseStore(hStore, 0);
}

答案 1 :(得分:1)

我不确定我们是否拥有所需的全部信息,因此我没有任何明确的答案。但是,让我们看看能否在解决方案方面取得进展。

您问:

  

我如何知道从哪个位置加载哪个证书?

作为验证者,您需要以某种形式拥有该受信任的证书。使用Windows,最好在证书存储区中安装该证书。这就是我在回答中所假设的。如果您不知道证书在哪里,则必须首先弄清楚该证书,否则您将无法正确验证签名。以某种方式,签名者需要将证书提供给验证者(您),通过受信任的渠道。

您还写道:

  

OpenSSL具有不错的命令openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -noverify,它确实可以完成我的工作   想做。

您确定这确实符合您的要求吗?由于您传递的是-noverify标志,因此此命令所做的全部工作就是验证LOCALSIG.DSA包含内容LOCALSIG.SF的正确签名。但是,它不会验证此签名是否由您信任的身份创建。拥有有效证书和密钥对的任何人都可以创建该签名。

为了使用stock-openssl版本验证签名(包括签名者的身份),您需要以openssl能够理解的格式(PEM或DER)提供完整的证书链,直到自己为止签名的根证书。然后您可以使用稍有不同的命令进行检查

openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -CAfile trusted-cert.pem

您的问题已更新,以指示签名证书是自签名的并且位于证书存储中。仍然有多种可能的途径,但是在这一点上,我将假定您知道您希望生成此签名的签名者的主题名称。 (如果没有,请指出您的期望)。有了这些信息,您可以使用以下步骤来验证您的签名:

首先使用CertEnumCertificatesInStore()在证书存储区中查找证书。有关用法示例,请参见Example C Program: Listing the Certificates in a Store

拥有该证书的句柄时,请使用CryptImportPublicKeyInfoEx2()获取所需公钥的句柄。

该句柄(类型为{{1}),可以与BCryptVerifySignature()一起使用进行验证。