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

bri*_*ery 20 c++ windows ssl winapi ssl-certificate

我想使用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,
                                   &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;
}
Run Code Online (Sandbox Code Playgroud)

Jan*_*ins 16

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

此外,这是一个(在我看来)相当值得信赖的参考:Chromium证书验证码.

总的来说,我认为你的代码并不正确.但是,如果我是你,我会调查/改变一些事情:

1.单独的通用名称验证

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.
Run Code Online (Sandbox Code Playgroud)

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可能跳过非root信任锚的撤销检查的可能性(Win32实际上需要信任锚自签名,即使任何RFC都不需要它.虽然这没有正式记录).

现在,由于根CA证书无法撤销(CRL无法签名/验证),因此在我看来这两个标志是相同的.

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

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

不幸:

  • 关于两面旗帜之间的差异,我找不到任何正式记录的解释.
  • 尽管链接的讨论可能适用于.NET引擎下的相同Win32 API标志,但不能保证.

为了完全确定可以使用CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,我搜索了一下,找到了我在回复顶部链接到的Chromium SSL证书验证码.

作为额外的好处,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.
Run Code Online (Sandbox Code Playgroud)

不知道他们怎么知道这个,但是在这一点上我觉得使用CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT感觉很舒服.

3.不同的接受证书用法

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

szOID_PKIX_KP_SERVER_AUTH,
szOID_SERVER_GATED_CRYPTO,
szOID_SGC_NETSCAPE
Run Code Online (Sandbox Code Playgroud)

从我可以通过谷歌收集的内容来看,旧的网络浏览器可能需要其他用法,否则他们可能无法建立安全连接.

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

请注意,如果更改代码,还应将params.RequestedUsage.dwType设置为USAGE_MATCH_TYPE_OR而不是USAGE_MATCH_TYPE_AND.

-

我现在想不出任何其他评论.但如果我是你,我会自己查看Chromium来源(也可能是Firefox) - 只是为了确保我没有错过任何东西.


Dan*_*Dan 1

我认为最好的答案取决于您到底想做什么。

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

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

现在考虑一下您希望从某个来源获得证书,但假装无法从可靠来源提供相同的信息。然后交给你。因此,您不能依赖证书来“证明”任何人是特定的任何人。

从证书获得的唯一保护是防止外部人员(而不是端点)破坏所传输消息的机密性。

任何其他用途都注定会失败,并且最终会失败并带来潜在的灾难性后果。

抱歉发了这么大的帖子。评论区有字数限制。

  • 感谢您的回答,虽然包含的信息有效,但我更希望有人可以在使用 Windows 加密 API 验证 SSL 使用证书的上下文中验证上述代码是否正确。我很清楚 SSL 在这些方面的局限性,但在这里我真正关心的是使我的端点绝对正确。对于上下文,我一直在为 PHP 核心开发 [这个补丁](https://github.com/php/php-src/pull/601) - 所以我们不担心特定的应用程序,只担心我们提供的工具是正确的。 (2认同)