WinHTTP中的SSL

WinHTTP中的SSL

Microsoft Windows HTTP服务(WinHTTP)支持安全套接字层(SSL)事务,包括客户端证书。 本主题说明SSL事务中涉及的概念以及如何使用WinHTTP处理它们。

0x01.安全链路层

SSL是确保安全HTTP事务的成熟标准。 SSL提供了一种机制,可对客户端和服务器之间的所有事务执行高达128位的加密。它使客户端能够通过使用服务器证书来验证服务器是否属于可信实体。它还使服务器能够使用客户端证书确认客户端的身份。
这些问题中的每一个(加密,服务器身份和客户端身份)都是在客户端首次从安全超文本传输​​协议(HTTPS)服务器请求资源时发生的SSL握手中协商的。基本上,客户端和服务器各自呈现所需和首选设置的列表。如果可以商定并满足一组公共要求,则建立SSL连接。
WinHTTP提供了使用SSL的高级接口。虽然SSL握手和事务的详细信息在内部处理,WinHTTP使您能够检索加密级别,指定安全协议,并与服务器和客户端证书交互。以下部分提供有关创建基于WinHTTP的应用程序的详细信息,这些应用程序选择SSL协议版本,检查服务器证书并选择要发送到HTTPS服务器的客户端证书。

0x02.服务器证书

服务器证书从服务器发送到客户端,以便客户端可以获得服务器的公钥,并确保服务器已经由证书颁发机构验证。证书可以包含不同类型的数据。例如,X.509证书包括证书的格式,证书的序列号,用于签署证书的算法,颁发证书的证书颁发机构(CA)的名称,证书的名称和公钥请求证书的实体,以及CA的签名。
当使用WinHTTP应用程序编程接口(API)时,您可以通过调用WinHttpQueryOption并指定WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT标志来检索服务器证书。服务器证书以WINHTTP_CERTIFICATE_INFO结构返回。如果您希望检索证书上下文,请改为指定WINHTTP_OPTION_SERVER_CERT_CONTEXT标志。
如果服务器证书包含错误,则可以在状态回调函数中获取有关错误的详细信息。 WINHTTP_CALLBACK_STATUS_SECURE_FAILURE通知指示服务器证书错误。 lpvStatusInformation参数包含一个或多个详细错误标志。有关详细信息,请参阅WINHTTP_STATUS_CALLBACK

0x03.客户端证书

SSL握手期间,服务器可能需要身份验证。通过向服务器提供有效的客户端证书来认证客户端。 WinHTTP使您能够从本地证书库中选择和发送证书。以下部分描述了在使用WinHTTP APIWinHttpRequest对象时提供客户端证书的过程。

0x04.WinHTTP API

WinHttpSendRequestWinHttpReceiveResponse都可能无法指示请求不成功,因为HTTPS服务器需要身份验证。在这些情况下,调用GetLastError以返回ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED。收到此错误时,请使用适当的CryptoAPI函数来查找适当的证书。指示此证书应与下一个请求一起发送,通过调用带有WINHTTP_OPTION_CLIENT_CERT_CONTEXT标志的WinHttpSetOption
以下代码示例说明如何在返回ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED错误后打开证书存储库并根据主题名称找到证书。

  if( !WinHttpReceiveResponse( hRequest, NULL ) )
  {
    if( GetLastError( ) == ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED )
    {
      //MY is the store the certificate is in.
      hMyStore = CertOpenSystemStore( 0, TEXT("MY") );
      if( hMyStore )
      {
        pCertContext = CertFindCertificateInStore( hMyStore,
             X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
             0,
             CERT_FIND_SUBJECT_STR,
             (LPVOID) szCertName, //Subject string in the certificate.
             NULL );
        if( pCertContext )
        {
          WinHttpSetOption( hRequest, 
                            WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
                            (LPVOID) pCertContext, 
                            sizeof(CERT_CONTEXT) );
          CertFreeCertificateContext( pCertContext );
        }
        CertCloseStore( hMyStore, 0 );

        // NOTE: Application should now resend the request.
      }
    }
  }

在重新发送包含客户端证书的请求之前,您可以确定支持的加密级别是否可以为应用程序接受。 调用WinHttpQueryOption并指定WINHTTP_OPTION_SECURITY_FLAGS标志以确定使用的加密级别。

0x05.SSL客户端认证的发行方列表检索

WinHttp客户端应用程序向需要SSL客户端认证的安全HTTP服务器发送请求时,如果应用程序没有提供客户端证书,WinHttp将返回ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED。对于在Windows Server 2008Windows Vista上运行的计算机,WinHttp使应用程序能够在身份验证质询中检索服务器提供的证书颁发者列表。发放者列表指定由服务器授权以颁发客户端证书的证书授权机构(CA)的列表。应用程序过滤发放方列表以获取所需的证书。
WinHttp客户端应用程序在WinHttpSendRequestWinHttpReceiveResponse返回ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED时检索发行者列表。当返回此错误时,应用程序使用WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST选项调用WinHttpQueryOptionlpBuffer参数必须足够大,以包含指向SecPkgContext_IssuerListInfoEx结构的指针。以下代码示例显示如何检索颁发者列表。

#include <windows.h>
#include <winhttp.h>
#include <schannel.h>

//...

void GetIssuerList(HINTERNET hRequest)
{
  SecPkgContext_IssuerListInfoEx* pIssuerList = NULL;
  DWORD dwBufferSize = sizeof(SecPkgContext_IssuerListInfoEx*);

  if (WinHttpQueryOption(hRequest,
           WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST,
           &pIssuerList,
           &dwBufferSize) == TRUE)
  {
    // Use the pIssuerList for cert store filtering.
    GlobalFree(pIssuerList); // Free the issuer list when done.
  }
}

SecPkgContext_IssuerListInfoEx结构中的信息cIssuersaIssuers可用于搜索证书,如下面的代码示例所示。有关详细信息,请参阅CertFindChainInStore

PCERT_CONTEXT pClientCert = NULL;
PCCERT_CHAIN_CONTEXT pClientCertChain = NULL;

CERT_CHAIN_FIND_BY_ISSUER_PARA SrchCriteria;
::ZeroMemory(&SrchCriteria, sizeof(CERT_CHAIN_FIND_BY_ISSUER_PARA));
SrchCriteria.cbSize = sizeof(CERT_CHAIN_FIND_BY_ISSUER_PARA);

SrchCriteria.cIssuer = pIssuerList->cIssuers;
SrchCriteria.rgIssuer = pIssuerList->aIssuers;

pClientCertChain = CertFindChainInStore(
            hClientCertStore,
            X509_ASN_ENCODING,
            CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG |
            // Do not perform wire download when building chains.
            CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG,
            // Do not search pCacheEntry->_ClientCertStore 
            // for issuer certs.
            CERT_CHAIN_FIND_BY_ISSUER,
            &SrchCriteria,
            NULL);

if (pClientCertChain)
{
    pClientCert = (PCERT_CONTEXT) pClientCertChain->rgpChain[0]->rgpElement[0]->pCertContext;

    CertDuplicateCertificateContext(pClientCert);

    CertFreeCertificateChain(pClientCertChain);

    pClientCertChain = NULL;
}

0x06.可选的客户端SSL证书

Windows Server 2008Windows Vista开始,WinHttp API支持可选的客户端证书。 当服务器请求客户端证书时,WinHttpSendRequestWinHttpRecieveResponse返回ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED错误。 如果服务器请求证书,但不需要证书,则应用程序可以指定此选项以指示它没有证书。 服务器可以选择另一种认证方案或允许匿名访问服务器。 应用程序在WinHttpSetOptionlpBuffer参数中指定WINHTTP_NO_CLIENT_CERT_CONTEXT宏,如以下代码示例所示。

BOOL fRet = WinHttpSetOption ( hRequest,                            WINHTTP_OPTION_CLIENT_CERT_CONTEXT,                         WINHTTP_NO_CLIENT_CERT_CONTEXT,
                               0);

如果设置了WINHTTP_NO_CLIENT_CERT_CONTEXT,并且服务器仍需要客户端证书,则它可能会发送403 HTTP状态代码。有关详细信息,请参阅WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST选项。

0x07.WinHttpRequest对象

使用WinHttpRequest对象的SetClientCertificate方法选择要通过请求发送到服务器的客户端证书。通过使用SetClientCertificate方法指定证书选择字符串来选择证书。证书选择字符串由证书位置,证书存储和由反斜杠分隔的主题名称组成。下表列出了此选择字符串的组件。

组件说明可能的值
Location确定存储证书的注册表项。可能的值为“LOCAL_MACHINE”,表示证书存储区在下HKEY_LOCAL_MACHINE和“CURRENT_USER”,以指示证书存储区处于非模拟状态HKEY_CURRENT_USER。此组件区分大小写。
Certificate store指示包含相关证书的证书存储的名称。典型的证书存储区是“MY”,“Root”和“TrustedPeople”。 此组件区分大小写。
Subject name题名标识指定证书存储区中的证书。 选择包含为此组件指定的字符串的第一个证书.主题名称可以是任何字符串。 空白字符串表示应使用证书存储库中的第一个证书。 此组件不区分大小写。

说明:

证书存储库名称和位置是可选组件。 但是,如果指定证书存储,则还必须指定该证书存储的位置。 默认位置为CURRENT_USER,默认证书存储区为“MY”。
以下代码示例显示如何指定应在HKEY_LOCAL_MACHINE下的注册表中的“个人”证书存储区中选择主题为“我的中间层证书”的证书。

HttpReq.SetClientCertificate("LOCAL_MACHINE\Personal\My Middle-Tier Certificate")
注意在某些语言中,反斜杠是转义字符。 请记住修改证书选择字符串以解决此问题。 例如,在Microsoft JScript中,使用两个相邻的反斜杠而不是一个。

如果未指定证书,并且HTTPS服务器需要客户端证书,则WinHTTP会选择默认证书存储库中的第一个证书。如果不存在证书,则会出现错误。如果未接受证书,则服务器返回403状态代码以指示无法满足请求。然后,您可以使用SetClientCertificate选择更适当的证书并重新发送请求。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用winhttp库在QT下使用https SSL post数据的示例代码: ```cpp #include <Windows.h> #include <Winhttp.h> #pragma comment(lib, "Winhttp.lib") void postData() { HINTERNET hSession = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwSize = 0; DWORD dwDownloaded = 0; LPSTR pszOutBuffer; BOOL bResults = FALSE; // Initialize WinHTTP session hSession = WinHttpOpen(L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { qDebug() << "WinHttpOpen failed!" << GetLastError(); goto cleanup; } // Specify an HTTPS server hConnect = WinHttpConnect(hSession, L"www.example.com", INTERNET_DEFAULT_HTTPS_PORT, 0); if (!hConnect) { qDebug() << "WinHttpConnect failed!" << GetLastError(); goto cleanup; } // Create an HTTPS request hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/post", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); if (!hRequest) { qDebug() << "WinHttpOpenRequest failed!" << GetLastError(); goto cleanup; } // Set request headers LPCWSTR pszHeaders = L"Content-Type: application/x-www-form-urlencoded\r\n"; bResults = WinHttpAddRequestHeaders(hRequest, pszHeaders, (DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD); if (!bResults) { qDebug() << "WinHttpAddRequestHeaders failed!" << GetLastError(); goto cleanup; } // Send the POST request LPCWSTR pszData = L"key1=value1&key2=value2"; bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)pszData, wcslen(pszData), wcslen(pszData), 0); if (!bResults) { qDebug() << "WinHttpSendRequest failed!" << GetLastError(); goto cleanup; } // Receive response from the server bResults = WinHttpReceiveResponse(hRequest, NULL); if (!bResults) { qDebug() << "WinHttpReceiveResponse failed!" << GetLastError(); goto cleanup; } // Read the server's response do { // Check for available data dwSize = 0; if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { qDebug() << "WinHttpQueryDataAvailable failed!" << GetLastError(); goto cleanup; } // Allocate space for the buffer pszOutBuffer = new char[dwSize + 1]; if (!pszOutBuffer) { qDebug() << "Out of memory!"; goto cleanup; } // Read the data ZeroMemory(pszOutBuffer, dwSize + 1); if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { qDebug() << "WinHttpReadData failed!" << GetLastError(); goto cleanup; } // Print the response to the console qDebug() << QString::fromLocal8Bit(pszOutBuffer); // Free the memory allocated to the buffer delete[] pszOutBuffer; } while (dwSize > 0); cleanup: // Close any open handles if (hRequest) WinHttpCloseHandle(hRequest); if (hConnect) WinHttpCloseHandle(hConnect); if (hSession) WinHttpCloseHandle(hSession); } ``` 以上代码,我们首先使用WinHttpOpen函数创建一个WinHTTP会话,然后使用WinHttpConnect函数连接到指定的HTTPS服务器。接着,我们使用WinHttpOpenRequest函数创建一个HTTPS请求,并使用WinHttpAddRequestHeaders函数设置请求头。然后,我们使用WinHttpSendRequest函数发送POST请求,并使用WinHttpReceiveResponse函数接收服务器的响应。最后,我们使用WinHttpQueryDataAvailable函数查询是否有可用的数据,使用WinHttpReadData函数读取服务器的响应,并使用qDebug打印到控制台上。注意,我们使用了QString::fromLocal8Bit函数将服务器的响应从char *转换为QString。最后,我们使用WinHttpCloseHandle函数关闭所有打开的句柄。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值