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

时间:2011-09-07 21:03:54

标签: c++ windows winapi ssl 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;
}

3 个答案:

答案 0 :(得分:15)

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

此外,这是(在我看来)相当值得信赖的参考:Chromium certificate verification code

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

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.

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.

不知道他们怎么知道这一点,但此时我觉得使用CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT感觉很舒服。

3。不同的接受证书用法

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

szOID_PKIX_KP_SERVER_AUTH,
szOID_SERVER_GATED_CRYPTO,
szOID_SGC_NETSCAPE

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

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

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

-

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

答案 1 :(得分:1)

我认为最好的答案取决于你究竟想要做什么。

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

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

现在考虑您希望从某个来源提供证书,但假装无法从可靠来源提供相同信息。然后交给你。所以你不能依靠证书“证明”任何人都是特别的人。

从证书中获得的唯一保护是防止外人而非终端破坏正在传输的邮件的机密性。

任何其他用途注定要失败,最终会因灾难性的后果而失败。

抱歉这篇文章很重要。评论部分有一个字数限制。

答案 2 :(得分:-1)

函数CertGetCertificateChainCertVerifyCertificatePolicy结合在一起。这部分是正确的。

对于CertGetCertificateChain,如果要检查撤销,可以将标志设置为以下三种中的任何一种:

  • CERT_CHAIN_REVOCATION_CHECK_END_CERT
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT。

只能使用其中一个,这三个选项不能是ORed。除了其中一个标志,你可以考虑如何创建链;使用local cacheCRLOCSP。出于这些考虑因素read this link

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

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

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

如果您选择来CertVerifyCertificatePolicy,则铬代码是关于如何将policy_status.dwError映射到错误类/枚举的精彩参考。