使用 WinCrypt 或 CNG 验证签名文件 (PKCS7)
Verify a signature file (PKCS7) with WinCrypt or CNG
我需要使用 Windows 加密 API 方法验证签名的 JAR 文件。我对加密和签名问题只有基本的了解。我也是那些加密 APIs(WinCrypt、Bcrypt、Ncrypt)的新手。验证文件哈希不是问题,但签名部分阻止了我。
感谢 OpenSSL、PKCS7 RFC (https://www.rfc-editor.org/rfc/rfc2315) 和其他各种来源,我能够找出 JAR 中包含的 META-INF/LOCALSIG.DSA 的实际文件内容。但是经过两周的挖掘、试验和错误,我仍然卡住了,不知道还能尝试什么。
OpenSSL 有很好的命令 openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -noverify
,它完全符合我的要求。不幸的是,我在 Windows API 中找不到这样的高级命令。
我尝试使用所有三个 API 中的 VerifySignature
函数族,但我需要为它们提供 public 键,但我没有成功使用任何 ImportKey
函数。因此,我尝试使用 CryptDecodeObjectEx
手动剖析 ASN1 格式,以便将各个部分作为 BLOB 传递给 API 函数。虽然我在这方面取得了一些成功,但我再次陷入困境,因为我不知道如何解析集合。我不想从头开始编写自己的 ASN1 解析器...
那么,如何使用带有 Windows 加密 API 的 PKCS7 签名文件?
我想使用 OpenSSL 可能更容易,但我必须说服我的雇主为了这个目的将 OpenSSL 添加到我们的代码库中...
UPDATE:LOCALSIG.DSA 文件包含签名者证书和 LOCALSIG.SF 文件的签名哈希.这可以使用 openssl pkcs7 -inform der -print_certs -text -in LOCALSIG.DSA
或 openssl cms -cmsout -inform DER -print -in LOCALSIG.DSA
.
进行验证
证书是我公司自签的,在证书库中。我可能需要提供整个信任链。这就是为什么我将 -noverify
选项添加到 openssl smime -verify
.
其实有两种不同证书(内部和外部版本)的场景,一种使用DSA(sig文件包含一个证书),另一种使用RSA(sig文件包含三个证书)。这意味着我无法硬编码要使用的证书或方法。我需要从提供的文件中提取该信息。我该怎么做?
我不确定我们是否拥有所有需要的信息,所以我没有任何明确的答案。但是,让我们看看能否在解决方案方面取得进展。
您问的是:
How do I know which certificate to load and from where?
作为验证者,您需要以某种形式拥有该可信证书。对于 Windows,最好将该证书安装在您的证书存储中。这是我在回答中假设的。如果你不知道证书在哪里,那你就得先弄清楚,否则你将无法正确验证signature.Somehow,证书需要由验证者提供给验证者(你)签名者,通过受信任的渠道。
您还写道:
OpenSSL has the nice command openssl smime -verify -inform der -in
LOCALSIG.DSA -content LOCALSIG.SF -noverify
, which does exactly what I
want to do.
你确定这正是你想要做的吗?由于您传递的是 -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()
to look up the certificate in the certificate store. For example usage, see Example C Program: Listing the Certificates in a Store.
当您拥有该证书的句柄后,使用 CryptImportPublicKeyInfoEx2()
获取所需 public 密钥的句柄。
该句柄类型为 BCRYPT_KEY_HANDLE
,您可以使用 with BCryptVerifySignature()
进行验证。
根据你的问题我了解到你需要使用分离签名验证 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);
}
我需要使用 Windows 加密 API 方法验证签名的 JAR 文件。我对加密和签名问题只有基本的了解。我也是那些加密 APIs(WinCrypt、Bcrypt、Ncrypt)的新手。验证文件哈希不是问题,但签名部分阻止了我。
感谢 OpenSSL、PKCS7 RFC (https://www.rfc-editor.org/rfc/rfc2315) 和其他各种来源,我能够找出 JAR 中包含的 META-INF/LOCALSIG.DSA 的实际文件内容。但是经过两周的挖掘、试验和错误,我仍然卡住了,不知道还能尝试什么。
OpenSSL 有很好的命令 openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -noverify
,它完全符合我的要求。不幸的是,我在 Windows API 中找不到这样的高级命令。
我尝试使用所有三个 API 中的 VerifySignature
函数族,但我需要为它们提供 public 键,但我没有成功使用任何 ImportKey
函数。因此,我尝试使用 CryptDecodeObjectEx
手动剖析 ASN1 格式,以便将各个部分作为 BLOB 传递给 API 函数。虽然我在这方面取得了一些成功,但我再次陷入困境,因为我不知道如何解析集合。我不想从头开始编写自己的 ASN1 解析器...
那么,如何使用带有 Windows 加密 API 的 PKCS7 签名文件?
我想使用 OpenSSL 可能更容易,但我必须说服我的雇主为了这个目的将 OpenSSL 添加到我们的代码库中...
UPDATE:LOCALSIG.DSA 文件包含签名者证书和 LOCALSIG.SF 文件的签名哈希.这可以使用 openssl pkcs7 -inform der -print_certs -text -in LOCALSIG.DSA
或 openssl cms -cmsout -inform DER -print -in LOCALSIG.DSA
.
证书是我公司自签的,在证书库中。我可能需要提供整个信任链。这就是为什么我将 -noverify
选项添加到 openssl smime -verify
.
其实有两种不同证书(内部和外部版本)的场景,一种使用DSA(sig文件包含一个证书),另一种使用RSA(sig文件包含三个证书)。这意味着我无法硬编码要使用的证书或方法。我需要从提供的文件中提取该信息。我该怎么做?
我不确定我们是否拥有所有需要的信息,所以我没有任何明确的答案。但是,让我们看看能否在解决方案方面取得进展。
您问的是:
How do I know which certificate to load and from where?
作为验证者,您需要以某种形式拥有该可信证书。对于 Windows,最好将该证书安装在您的证书存储中。这是我在回答中假设的。如果你不知道证书在哪里,那你就得先弄清楚,否则你将无法正确验证signature.Somehow,证书需要由验证者提供给验证者(你)签名者,通过受信任的渠道。
您还写道:
OpenSSL has the nice command
openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -noverify
, which does exactly what I want to do.
你确定这正是你想要做的吗?由于您传递的是 -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()
to look up the certificate in the certificate store. For example usage, see Example C Program: Listing the Certificates in a Store.
当您拥有该证书的句柄后,使用 CryptImportPublicKeyInfoEx2()
获取所需 public 密钥的句柄。
该句柄类型为 BCRYPT_KEY_HANDLE
,您可以使用 with BCryptVerifySignature()
进行验证。
根据你的问题我了解到你需要使用分离签名验证 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);
}