在 Win32 中验证 SSL 证书的正确方法是什么?
我想使用 C++ 在 Win32 中验证 SSL 证书。我想我想使用 Cert* API,以便获得 Windows 证书存储的好处。这就是我想出的办法。
- 正确吗?
- 有更好的方法吗?
- 我做错了什么吗?
bool IsValidSSLCertificate( PCCERT_CONTEXT certificate, LPWSTR serverName )
{
LPTSTR usages[] = { szOID_PKIX_KP_SERVER_AUTH };
CERT_CHAIN_PARA params = { sizeof( params ) };
params.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
params.RequestedUsage.Usage.cUsageIdentifier = _countof( usages );
params.RequestedUsage.Usage.rgpszUsageIdentifier = usages;
PCCERT_CHAIN_CONTEXT chainContext = 0;
if ( !CertGetCertificateChain( NULL,
certificate,
NULL,
NULL,
¶ms,
CERT_CHAIN_REVOCATION_CHECK_CHAIN,
NULL,
&chainContext ) )
{
return false;
}
SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslPolicy = { sizeof( sslPolicy ) };
sslPolicy.dwAuthType = AUTHTYPE_SERVER;
sslPolicy.pwszServerName = serverName;
CERT_CHAIN_POLICY_PARA policy = { sizeof( policy ) };
policy.pvExtraPolicyPara = &sslPolicy;
CERT_CHAIN_POLICY_STATUS status = { sizeof( status ) };
BOOL verified = CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL,
chainContext,
&policy,
&status );
CertFreeCertificateChain( chainContext );
return verified && status.dwError == 0;
}
I want to verify an SSL certificate in Win32 using C++. I think I want to use the Cert* API so that I can get the benefit of the Windows certificate store. This is what I've come up with.
- Is it correct?
- Is there a better way to do this?
- Am I doing anything wrong?
bool IsValidSSLCertificate( PCCERT_CONTEXT certificate, LPWSTR serverName )
{
LPTSTR usages[] = { szOID_PKIX_KP_SERVER_AUTH };
CERT_CHAIN_PARA params = { sizeof( params ) };
params.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
params.RequestedUsage.Usage.cUsageIdentifier = _countof( usages );
params.RequestedUsage.Usage.rgpszUsageIdentifier = usages;
PCCERT_CHAIN_CONTEXT chainContext = 0;
if ( !CertGetCertificateChain( NULL,
certificate,
NULL,
NULL,
¶ms,
CERT_CHAIN_REVOCATION_CHECK_CHAIN,
NULL,
&chainContext ) )
{
return false;
}
SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslPolicy = { sizeof( sslPolicy ) };
sslPolicy.dwAuthType = AUTHTYPE_SERVER;
sslPolicy.pwszServerName = serverName;
CERT_CHAIN_POLICY_PARA policy = { sizeof( policy ) };
policy.pvExtraPolicyPara = &sslPolicy;
CERT_CHAIN_POLICY_STATUS status = { sizeof( status ) };
BOOL verified = CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL,
chainContext,
&policy,
&status );
CertFreeCertificateChain( chainContext );
return verified && status.dwError == 0;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您应该了解 RFC3280 第 6.1 节 和 RFC5280 第 6.1 节。两者都描述了验证证书路径的算法。尽管 Win32 API 会为您处理一些事情,但总体了解该过程仍然很有价值。
另外,这里有一个(在我看来)非常值得信赖的参考: Chromium证书验证码。
总的来说,我认为您的代码没有错误。但如果我是你,我会研究/更改以下几件事:
1. 单独的公用名验证
Chromium 独立于链验证证书公用名。显然他们已经注意到了一些问题。请参阅注释以了解其基本原理:
2. 使用 CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
Chromium 还使用 CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT 标志而不是 CERT_CHAIN_REVOCATION_CHECK_CHAIN。实际上,在找到他们的代码之前,我就开始研究这一点,这增强了我的信念,即您应该使用 CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT。
尽管上述两个 RFC 都指定自签名信任锚不被视为链的一部分,但 CertGetCertificateChain 的文档 (http://msdn.microsoft.com/en-us/library/windows/desktop/aa376078(v=vs.85).aspx) 说它建立了一条链,如果可能的话,受信任的根证书。受信任的根证书被定义为(在同一页面上)作为受信任的自签名证书。
这消除了 *EXCLUDE_ROOT 可能跳过非根信任锚的撤销检查的可能性(Win32 实际上要求信任锚进行自签名,即使任何 RFC 都没有要求。尽管这没有正式记录)。
现在,由于根 CA 证书无法撤销自身(无法签署/验证 CRL),因此在我看来这两个标志是相同的。
我做了一些谷歌搜索,偶然发现了这个论坛帖子:http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/9f95882a-1a68-477a-80ee-0a7e3c7ae5cf/x509replicationflag-question?forum=windowssecurity。 .NET 产品组的一名成员(据说)声称,如果根是自签名的,那么这些标志在实践中的作用是相同的(理论上,如果根证书包含 CDP 扩展,则 ENTIRE_CHAIN 标志将检查根证书是否被吊销,但是不可能发生)。
他还建议使用 *EXCLUDE_ROOT 标志,因为如果自签名根 CA 包含 CDP 扩展,则另一个标志可能会导致不必要的网络请求。
不幸的是:
为了完全确定可以使用 CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,我在 google 上进行了更多搜索,找到了我在回复顶部链接到的 Chromium SSL 证书验证代码。
作为额外的好处,Chromium cert_verify_proc_win.cc 文件包含以下有关 IE 验证代码的提示:
不确定他们如何知道这一点,但此时我觉得使用 CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT 很舒服。
3. 接受的不同证书用法
我注意到 Chromium 还指定了 3 种证书用法,而不是 1 种:
根据我通过 Google 收集到的信息,较旧的 Web 浏览器可能需要其他用法,否则它们可能无法建立安全连接。
如果 Chromium 认为适合包含这些用法,我会效仿。
请注意,如果更改代码,还应将 params.RequestedUsage.dwType 设置为 USAGE_MATCH_TYPE_OR 而不是 USAGE_MATCH_TYPE_AND。
——
我现在想不出任何其他评论。但如果我是你,我会自己检查 Chromium 源代码(也许还有 Firefox)——只是为了确保我没有错过任何东西。
You should be aware of RFC3280 section 6.1 and RFC5280 section 6.1. Both describe algorithms for validating certificate paths. Even though Win32 API takes care of some things for you, it could still be valuable to know about the process in general.
Also, here’s a (in my opinion) pretty trustworthy reference: Chromium certificate verification code.
Overall, I think your code isn't incorrect. But here’s a few things I’d look into/change, if I were you:
1. Separate Common Name Validation
Chromium validates certificate common name separately from the chain. Apparently they've noticed some problems with it. See the comments for their rationale:
2. Use CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
Chromium also uses the CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT flag instead of CERT_CHAIN_REVOCATION_CHECK_CHAIN. I actually started to looking into this before I found their code, and it reinforced my belief that you should use CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT.
Even though both aforementioned RFCs specify that a self-signed trust anchor is not considered part of a chain, the documentation for CertGetCertificateChain (http://msdn.microsoft.com/en-us/library/windows/desktop/aa376078(v=vs.85).aspx) says it builds a chain up to, if possible, a trusted root certificate. A trusted root certificate is defined (on the same page) as a trusted self-signed certificate.
This eliminates the possibility that *EXCLUDE_ROOT might skip revocation checking for a non-root trust anchor (Win32 actually requires trust-anchors to be self-signed, even though it is not required by any RFCs. Though this is not officially documented).
Now, since a root CA certificate can not revoke itself (the CRL could not be signed/verified), it seems to me that these two flags are identical.
I did some googling and stumbled across this forum post: http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/9f95882a-1a68-477a-80ee-0a7e3c7ae5cf/x509revocationflag-question?forum=windowssecurity. A member of .NET Product Group (supposedly) claims that the flags in practice act the same, if the root is self-signed (in theory, the ENTIRE_CHAIN flag would check the root certificate for revocation if it included a CDP extension, but that can’t happen).
He also recommends to use the *EXCLUDE_ROOT flag, because the other flag could cause an unnecessary network request, if the self-signed root CA includes the CDP extension.
Unfortunately:
To be completely sure that it’s ok to use CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, I googled a bit more and found the Chromium SSL certificate verification code I linked to at the top of my reply.
As an added bonus, the Chromium cert_verify_proc_win.cc file contains the following hints about IE verification code:
Not sure how they’d know this, but at this point I’d feel comfortable using CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT.
3. Different Accepted Certificate Usages
I noticed Chromium also specifies 3 certificate usages instead of 1:
From what I can gather through Google, the other usages can be required by older web browsers, otherwise they can fail to establish a secure connection.
If Chromium deems fit to include these usages, I'd follow suit.
Note that if you change your code, you should also set params.RequestedUsage.dwType to USAGE_MATCH_TYPE_OR instead of USAGE_MATCH_TYPE_AND.
—
I can’t think of any other comments at the moment. But if I were you, I’d check out Chromium source myself (and maybe Firefox too) - just to be sure I haven’t missed anything.
我认为最好的答案取决于您到底想做什么。
我要提醒您的是,SSL 基于两个端点都需要安全连接的假设。如果任一端点对维护安全性不感兴趣,那么就没有端点。
将字节码放入分布式代码中只需为此函数返回 true 即可,这是一项微不足道的工作。这就是 Windows 将大量验证移入内核的原因。但他们没有预料到人们会在虚拟硬件上运行 Windows,这使得绕过操作系统变得微不足道。
现在考虑一下您希望从某个来源获得证书,但假装无法从可靠来源提供相同的信息。然后交给你。因此,您不能依赖证书来“证明”任何人是特定的任何人。
从证书获得的唯一保护是防止外部人员(而不是端点)破坏所传输消息的机密性。
任何其他用途都注定会失败,并且最终会失败并带来潜在的灾难性后果。
抱歉发了这么大的帖子。评论区有字数限制。
I think the best answer depends on what exactly you are attempting to do.
I will caution you that SSL is based on the assumption that Both endpoints want a secure connection. If either endpoint isn't interested in maintaining security then there is none.
Its a trivial effort to put byte codes in your distributed code that simply returns true for this function. That's why windows moved a lot of validation into the kernel. But they didn't anticipate people running windows on virtual hardware, which makes circumventing the OS just about as trivial.
Now consider that you expect to be provided a cert from some source, but pretending that that source couldn't be provided the same information from a reliable source. And then hand it to you. So You cannot rely on certificates to "prove" anyone is anyone in particular.
The only protection gained from certificates are in preventing outsiders, not endpoints, from breaching the confidentiality of the message being transported.
Any other use is doomed to fail, and it will fail eventually with potentially catastrophic results.
Sorry for the big post. The comment section has a word limit.
函数
CertGetCertificateChain
和CertVerifyCertificatePolicy
一起使用。这部分是正确的。对于
CertGetCertificateChain
,如果您想要检查吊销情况,则可以将标志设置为以下三个中的任意一个:只能使用其中之一,这三个选项不能进行
ORed
。除了这些标志之一之外,您还可以考虑如何创建链;使用本地缓存
或仅使用CRL
或OCSP
。对于这些注意事项阅读此链接。执行函数时出错,或者更简单地说,如果返回值为
0
,这并不意味着证书无效,而是您无法执行该操作。要获取错误信息,请使用GetLastError()
。所以你返回 false 的逻辑是错误的,它更多的是抛出错误并让客户端代码决定是重试还是继续做其他事情。在此链接中有名为“对错误进行分类”的部分,请阅读。基本上你应该检查 certChainContext->TrustStatus.dwErrorStatus。这里将对错误状态列表进行“或”运算。请检查 CERT_TRUST_STATUS msdn 参考。所以在这里你可以拥有你的业务逻辑。例如,如果您发现无法执行证书吊销检查的值 (
CERT_TRUST_REVOCATION_STATUS_UNKNOWN | CERT_TRUST_IS_OFFLINE_REVOCATION
) 的错误状态,您可以选择决定您想要什么(放弃证书或仍然标记)视为无效)。因此,在调用
CertVerifyCertificatePolicy
之前,您可以选择放弃或已经标记验证错误。如果您选择使用
CertVerifyCertificatePolicy
,那么 chromium 代码是关于如何将policy_status.dwError 映射到您的错误类/枚举的绝佳参考。The functions
CertGetCertificateChain
andCertVerifyCertificatePolicy
go together. This part is correct.For
CertGetCertificateChain
the flag can be set to any of the following three if you want to check for revocation:Only one of them can be used, these three options cannot be
ORed
. Beside one of these flags you can consider how the chain should be created; usinglocal cache
or justCRL
orOCSP
. For these considerations read this link.Error in executing the function or more simply if the return value is
0
, it does not mean the certificate is invalid, rather you were unable to perform the operation. For error information useGetLastError()
. So your logic of returning false is wrong, it is more of a case of throwing the error and let the client code decide whether to try again or go on to do other stuff.In this link there is a section called "classify the error", please read that. Basically you should check
certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT_TRUST_STATUS
msdn reference. So here you can have your business logic. For example, if you find the error status of the value (CERT_TRUST_REVOCATION_STATUS_UNKNOWN | CERT_TRUST_IS_OFFLINE_REVOCATION
) that certificate revocation check could not be performed, you have the option to decide what you want (let the cert go or still mark it as invalid).So, before going to call
CertVerifyCertificatePolicy
you have the option to discard or already flag a validation error.If you choose to come to
CertVerifyCertificatePolicy
, the chromium code is a wonderful reference regarding how to map policy_status.dwError to your error class/enum.