windows 在 Win32 中验证 SSL 证书的正确方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7340504/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 18:01:55  来源:igfitidea点击:

What's the correct way to verify an SSL certificate in Win32?

c++windowswinapisslssl-certificate

提问by briangreenery

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.

我想使用 C++ 在 Win32 中验证 SSL 证书。我想我想使用 Cert* API 以便我可以获得 Windows 证书存储的好处。这是我想出的。

  • 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,
                                   &params,
                                   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;
}

回答by Janis Kirsteins

You should be aware of RFC3280 section 6.1and 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.

您应该了解RFC3280 第 6.1 节RFC5280 第 6.1 节。两者都描述了验证证书路径的算法。尽管 Win32 API 为您处理了一些事情,但总体上了解该过程仍然很有价值。

Also, here's a (in my opinion) pretty trustworthy reference: Chromium certificate verification code.

另外,这里有一个(在我看来)非常值得信赖的参考:Chromium 证书验证码

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

1. 单独的通用名称验证

Chromium validates certificate common name separately from the chain. Apparently they've noticed some problems with it. See the comments for their rationale:

Chromium 验证证书公用名与链分开。显然他们已经注意到了一些问题。请参阅评论以了解其理由:

cert_verify_proc.win.cc:731 // Certificate name validation happens separately, later, using an internal
cert_verify_proc.win.cc:732 // routine that has better support for RFC 6125 name matching.

2. Use CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT

2. 使用 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.

Chromium 还使用 CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT 标志而不是 CERT_CHAIN_REVOCATION_CHECK_CHAIN。我实际上在找到他们的代码之前就开始研究这个问题,它强化了我的信念,即您应该使用 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.

尽管上述两个 RFC 都指定自签名信任锚不被视为链的一部分,但 CertGetCertificateChain 的文档 ( http://msdn.microsoft.com/en-us/library/windows/desktop/aa376078(v= vs.85).aspx) 说它建立一个链,如果可能的话,一个受信任的根证书。受信任的根证书(在同一页面上)被定义为受信任的自签名证书。

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).

这消除了 *EXCLUDE_ROOT 可能跳过对非根信任锚的撤销检查的可能性(Win32 实际上要求信任锚自签名,即使任何 RFC 都不需要它。尽管这没有正式记录)。

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.

现在,由于根 CA 证书无法自行撤销(无法签署/验证 CRL),在我看来,这两个标志是相同的。

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).

我做了一些谷歌搜索并偶然发现了这个论坛帖子:http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/9f95882a-1a68-477a-80ee-0a7e3c7ae5cf/x509revocationflag-question?forum= windowssecurity。.NET 产品组的一名成员(据称)声称,如果根是自签名的,则这些标志在实践中的作用相同(理论上,如果根证书包含 CDP 扩展,则 ENTIRE_CHAIN 标志将检查是否吊销根证书,但是不可能发生)。

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.

他还建议使用 *EXCLUDE_ROOT 标志,因为如果自签名根 CA 包含 CDP 扩展,另一个标志可能会导致不必要的网络请求。

Unfortunately:

很遗憾:

  • I can't find any officially documented explanation on the differences between the two flags.
  • Even though it is likely that the linked discussion applies to the same Win32 API flags under the hood of .NET, it is not guaranteed.
  • 我找不到任何关于这两个标志之间差异的官方文件解释。
  • 即使链接的讨论很可能适用于 .NET 引擎盖下的相同 Win32 API 标志,但不能保证。

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.

为了完全确定可以使用 CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,我在谷歌上搜索了更多,并在我的回复顶部找到了链接到的 Chromium SSL 证书验证码。

As an added bonus, the Chromium cert_verify_proc_win.cc file contains the following hints about IE verification code:

作为额外的奖励,Chromium cert_verify_proc_win.cc 文件包含以下有关 IE 验证码的提示:

618: // IE passes a non-NULL pTime argument that specifies the current system
619: // time.  IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the
620: // chain_flags argument.

Not sure how they'd know this, but at this point I'd feel comfortable using CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT.

不知道他们是如何知道这一点的,但在这一点上我觉得使用 CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT 会很舒服。

3. Different Accepted Certificate Usages

3. 不同的接受证书用法

I noticed Chromium also specifies 3 certificate usages instead of 1:

我注意到 Chromium 还指定了 3 个证书用法而不是 1 个:

szOID_PKIX_KP_SERVER_AUTH,
szOID_SERVER_GATED_CRYPTO,
szOID_SGC_NETSCAPE

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.

从我通过 Google 收集的信息来看,旧版网络浏览器可能需要其他用法,否则它们可能无法建立安全连接。

If Chromium deems fit to include these usages, I'd follow suit.

如果 Chromium 认为适合包含这些用法,我会效仿。

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.

请注意,如果您更改代码,您还应该将 params.RequestedUsage.dwType 设置为 USAGE_MATCH_TYPE_OR 而不是 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.

我暂时想不出任何其他评论。但如果我是你,我会自己查看 Chromium 源代码(也许还有 Firefox)——只是为了确保我没有遗漏任何东西。

回答by Dan

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.

我要提醒您的是,SSL 是基于两个端点都需要安全连接的假设。如果任一端点对维护安全性不感兴趣,则没有。

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.

将字节码放入您的分布式代码中是一项微不足道的努力,只需为此函数返回 true。这就是 Windows 将大量验证移入内核的原因。但他们没有预料到人们会在虚拟硬件上运行 Windows,这使得绕过操作系统变得微不足道。

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.

对不起,大帖子。评论区有字数限制。

回答by Mehrab Monjur

The functions CertGetCertificateChainand CertVerifyCertificatePolicygo together. This part is correct.

功能CertGetCertificateChainCertVerifyCertificatePolicy走在一起。这部分是正确的。

For CertGetCertificateChainthe flag can be set to any of the following three if you want to check for revocation:

对于CertGetCertificateChain该标志可以被设置为任意的以下三种,如果你想检查撤销:

  • CERT_CHAIN_REVOCATION_CHECK_END_CERT
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT.
  • CERT_CHAIN_REVOCATION_CHECK_END_CERT
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT。

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; using local cacheor just CRLor OCSP. For these considerations read this link.

只能使用其中之一,不能使用这三个选项ORed。除了这些标志之一,您还可以考虑如何创建链;使用local cache或 只是CRLOCSP。对于这些考虑,请阅读此链接

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 use GetLastError(). 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.

执行函数时出错或更简单地如果返回值为0,这并不意味着证书无效,而是您无法执行操作。有关错误信息,请使用GetLastError(). 所以你返回 false 的逻辑是错误的,更多的是抛出错误并让客户端代码决定是再试一次还是继续做其他事情。

In this linkthere 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_STATUSmsdn 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).

此链接中有一个名为“分类错误”的部分,请阅读。基本上你应该检查certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT_TRUST_STATUSmsdn 参考。所以在这里你可以拥有你的业务逻辑。例如,如果您发现CERT_TRUST_REVOCATION_STATUS_UNKNOWN | CERT_TRUST_IS_OFFLINE_REVOCATION无法执行证书吊销检查的值 ( )的错误状态,您可以选择决定您想要什么(放弃证书或仍然将其标记为无效)。

So, before going to call CertVerifyCertificatePolicyyou have the option to discard or already flag a validation error.

因此,在致电之前,CertVerifyCertificatePolicy您可以选择放弃或已标记验证错误。

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.

如果你选择来CertVerifyCertificatePolicy,铬代码是关于如何将 policy_status.dwError 映射到你的错误类/枚举的一个很好的参考。