2011-09-07 155 views
19

我想使用C++在Win32中验证SSL证书。我想我想使用Cert * API,这样我就可以获得Windows证书存储的好处。这是我想出来的。在Win32中验证SSL证书的正确方法是什么?

  • 它是正确的吗?
  • 有没有更好的方法来做到这一点?
  • 我做错了什么?
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; 
} 
+0

你没有提到你使用的是什么,但是,如果你在典型的HTTPS场景中使用它,通常应该传递CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT。 – EricLaw

+0

我主要想用它来验证LDAP服务器SSL证书(比如,在VERIFYSERVERCERT函数中)。我也想用它来验证客户端/服务器应用程序中的HTTPS服务器证书,客户端可以为服务器指定自己的SSL证书。 – briangreenery

+0

使用CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT而不是CERT_CHAIN_REVOCATION_CHECK_CHAIN更常见吗?你为什么不检查根证书的撤销? – briangreenery

回答

1

我认为最好的答案取决于正是你正在尝试做的。

我会告诫你,SSL是基于两个终端都需要安全连接的假设。如果任一端点对维护安全性不感兴趣,则不存在。

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

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

从证书中获得的唯一保护是防止外部人员而不是端点破坏被传输邮件的机密性。

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

对不起,大帖子。评论部分有一个字数限制。

+2

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

14

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

此外,这里是一个(在我看来)非常值得信赖的参考:Chromium certificate verification code。总体而言,我认为你的代码并不正确。但这里有几件事情我想看看/改变,如果我是你:

1.独立的通用名称验证

铬从环比分别验证证书的通用名称。显然他们已经注意到它的一些问题。见他们的理由的评论:

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

铬也使用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可能跳过对非根信任锚点的撤销检查(即使它不是任何RFC的要求,但实际上需要信任锚点是自签名的)。记录)。

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

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

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

不幸的是:

  • 我无法找到这两个标志之间的差异的任何正式文件解释。
  • 尽管链接的讨论可能适用于.NET引擎下的相同Win32 API标志,但不能保证。

完全确信它的确定使用CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,我用Google搜索了一点,发现铬SSL证书验证码,我挂在我的答复的顶部。

作为一个额外的奖励,铬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.不同接受证书用法

我注意到铬还规定3层证书的用途,而不是1:

szOID_PKIX_KP_SERVER_AUTH, 
szOID_SERVER_GATED_CRYPTO, 
szOID_SGC_NETSCAPE 

从我可以通过谷歌收集的其他用途可以由旧版本的网络要求浏览器,否则他们可能无法建立安全连接。

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

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

-

我想不出任何其他意见。但如果我是你,我会自己检查Chromium源(也可能是Firefox) - 只是为了确保我没有遗漏任何东西。

-1

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

  • CERT_CHAIN_REVOCATION_CHECK_END_CERT
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT:

    对于CertGetCertificateChain的标志可以,如果你想检查撤销设置为任何有以下三种。

只有其中一个可以使用,这三个选项不能是ORed。除了这些旗帜之一,您可以考虑如何创建链条;使用local cache或者仅使用CRLOCSP。对于这些考虑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映射到您的错误类/枚举的绝妙参考。