在 WinINet 中手动验证服务器证书

Posted

技术标签:

【中文标题】在 WinINet 中手动验证服务器证书【英文标题】:Manually validate server certificate in WinINet 【发布时间】:2009-04-08 22:19:34 【问题描述】:

我正在尝试对 WinINet 客户端实施手动自签名 SSL 证书验证。我试图通过使用INTERNET_OPTION_SECURITY_CERTIFICATEINTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT 参数调用InternetQueryOption 来接近它,但是两者都返回服务器证书的一些内部解释,没有一个允许访问原始证书公钥或至少是拇指指纹。

我应该如何验证证书?...

【问题讨论】:

【参考方案1】:

除了上一个答案。 如果您想手动检查具有不受信任的根的证书(例如自签名),您需要

    设置标志以忽略不受信任的证书
// Open request before
HINTERNET hRequest = HttpOpenRequest(hConnect, _T("POST"), action, NULL, NULL, NULL, dwFlags, 0);
if (!hRequest) return GetLastError();

// set ignore options to request
DWORD dwFlags;
DWORD dwBuffLen = sizeof(dwFlags);
InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, (LPVOID)&dwFlags, &dwBuffLen);
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
InternetSetOption (hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags));
    发送数据请求
if (HttpSendRequest(hRequest, strHeaders, strHeaders.GetLength(), data, len)) 
    只有在数据请求返回后,我们才能查看证书信息并检查指纹。 要获取指纹,我们必须使用 cryptapi 中的方法, 所以需要#include "WinCrypt.h" 并将 crypt32.lib 添加到链接器
PCCERT_CHAIN_CONTEXT CertCtx=NULL;
DWORD cbCertSize = sizeof(&CertCtx);

// Get certificate chain information
if (InternetQueryOption(hRequest, INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT, (LPVOID)&CertCtx, &cbCertSize))

    PCCERT_CHAIN_CONTEXT pChainContext=CertCtx;

    CERT_SIMPLE_CHAIN *simpleCertificateChainWithinContext = NULL;
    for (int i=0; i<pChainContext->cChain; i++)
    
        simpleCertificateChainWithinContext=pChainContext->rgpChain[i];
        
        // We can check any certificates from chain, but if selfsigned it will be single
        for (int simpleCertChainIndex = 0; simpleCertChainIndex < simpleCertificateChainWithinContext->cElement; impleCertChainIndex++)
        
            // get the CertContext from the array
            PCCERT_CONTEXT pCertContext = simpleCertificateChainWithinContext->rgpElement[simpleCertChainIndex]->pCertContext;
            
            // Public key can be getted from
            //  (((*((*pCertContext).pCertInfo)).SubjectPublicKeyInfo).PublicKey).pbData
            // but better to use thumbprint to check

            // CERT_HASH_PROP_ID - is a thumbprint
            BYTE thumbprint[1024];
            DWORD len = 1024;
            if (CertGetCertificateContextProperty(pCertContext, CERT_HASH_PROP_ID, thumbprint, &len)) 
                //
                // !!! HERE WE CAN CHECK THUMPRINT WITH TRUSTED(PREVIOUSLY SAVED)
                // and return error, or accept request output.
                //
            
        
    
    
    // important! Free the CertCtx
    CertFreeCertificateChain(CertCtx);

    为什么使用指纹来比较证书? 证书中有三个有趣的领域可以比较: 序列号、公钥、指纹。 序列号只是颁发证书的 CA 选择的唯一编号, 公钥很好的解决方案,但指纹是在完整证书上计算的哈希值 (https://security.stackexchange.com/questions/35691/what-is-the-difference-between-serial-number-and-thumbprint)

【讨论】:

【参考方案2】:

如果您在调用 HttpOpenRequest 时设置了 INTERNET_FLAG_SECURE,WinInet 将已经验证返回的证书的域名是否与证书匹配并且证书链是受信任的。

之后你可以做的几件事:

    使用 INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT 的 lpszIssuerInfo 将返回的域名和证书名称与预期的父证书匹配。

    从 lpszIssuerInfo 中解析出颁发者名称并调用 CertFindCertificateInStore 以获取证书上下文指针。

    使用 CertGetCertificateChain 和证书上下文指针获取和验证证书链,例如比较颁发证书的指纹,而不是我所知道的实际证书本身。

供将来参考,来自 MSDN:“http://msdn.microsoft.com/en-us/library/aa385328(VS.85).aspx”。如果安装了 IE8.0,则有一个新选项可以公开服务器的证书链。

INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT 105

检索服务器的证书链上下文作为重复的 PCCERT_CHAIN_CONTEXT。您可以将此重复的上下文传递给任何采用 PCCERT_CHAIN_CONTEXT 的 Crypto API 函数。完成证书链上下文后,您必须在返回的 PCCERT_CHAIN_CONTEXT 上调用 CertFreeCertificateChain。

版本:需要 Internet Explorer 8.0。

【讨论】:

我已经有一段时间没有处理这个问题了,但据我所知,我需要让 wininet 使用私人自行颁发的证书。因此,证书将不会被存储,并且链将不被信任。我想以编程方式验证自己的信任。我记得,WinINet 没有授予对原始证书或指纹的访问权限,而是授予主题名称和颁发者名称等。任何人都可以生成具有给定主题名称或颁发者名称的证书。

以上是关于在 WinINet 中手动验证服务器证书的主要内容,如果未能解决你的问题,请参考以下文章

Wininet - 如何下载和验证 SSL 证书

手动验证 SSL 自签名证书签名 - javascript [重复]

如何在不强制断开连接的情况下使用 Go TLS 手动验证客户端证书?

OpenSSL WinINET 客户端

即使在 SpringMVC 中包含密钥库证书后也无法验证服务器

即使在 SpringMVC 中包含密钥库证书后也无法对服务器进行身份验证