WinHTTP 中的 SSL

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

安全套接字层 (Secure Sockets Layer)

SSL 是确保 HTTP 事务安全的既定标准。 SSL 提供了一种机制,用于对客户端和服务器之间的所有事务执行最多 128 位加密。 它使客户端能够通过使用服务器证书来验证服务器是否属于受信任的实体。 它还允许服务器使用客户端证书确认客户端的身份。

加密、服务器标识和客户端标识中的每一个问题在 SSL 握手中协商,当客户端首次从安全超文本传输协议请求资源时, (HTTPS) 服务器。 实质上,客户端和服务器都显示必需设置和首选设置的列表。 如果可以同意并满足一组常见的要求,则会建立 SSL 连接。

WinHTTP 提供用于使用 SSL 的高级接口。 虽然 SSL 握手和事务的详细信息是在内部处理的,但 WinHTTP 允许你检索加密级别、指定安全协议以及与服务器和客户端证书交互。 以下部分提供有关创建基于 WinHTTP 的应用程序的详细信息,这些应用程序选择 SSL 协议版本、检查服务器证书以及选择要发送到 HTTPS 服务器的客户端证书。

服务器证书

服务器证书从服务器发送到客户端,以便客户端可以获取服务器的公钥,并确保服务器已经过证书颁发机构的验证。 证书可以包含不同类型的数据。 例如,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

客户端证书

在 SSL 握手期间,服务器可能需要身份验证。 客户端通过向服务器提供有效的客户端证书进行身份验证。 WinHTTP 允许你从本地证书存储中选择和发送 证书。 以下部分介绍在使用 WinHTTP API 或 WinHttpRequest 对象时提供客户端证书的过程。

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 标志以确定使用的加密级别。

SSL 客户端身份验证的颁发者列表检索

当 WinHttp 客户端应用程序向需要 SSL 客户端身份验证的安全 HTTP 服务器发送请求时,如果应用程序未提供客户端证书,WinHttp 将返回 ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED 。 对于在 Windows Server 2008 和 Windows Vista 上运行的计算机,WinHttp 使应用程序能够检索服务器在身份验证质询中提供的证书颁发者列表。 颁发者列表指定由服务器授权颁发客户端证书的证书颁发机构 (CA) 的列表。 应用程序筛选颁发者列表以获取所需的证书。

WinHttpSendRequestWinHttpReceiveResponse 返回ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED时,WinHttp 客户端应用程序检索颁发者列表。 返回此错误时,应用程序使用 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;
}

可选的客户端 SSL 证书

从 Windows Server 2008 和 Windows 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 选项。

WinHttpRequest 对象

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

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

证书存储名称和位置是可选组件。 但是,如果指定 证书存储,还必须指定该 证书存储的位置。 默认位置为 CURRENT_USER,默认 证书存储 为“MY”。

下面的代码示例演示如何指定应从注册表中的“个人” 证书存储 HKEY_LOCAL_MACHINE下选择主题为“我的 Middle-Tier证书”。

HttpReq.SetClientCertificate("LOCAL_MACHINE\Personal\My Middle-Tier Certificate")

注意

在某些语言中,反斜杠是转义字符。 请记得修改证书选择字符串来解释这一点。 例如,在 Microsoft JScript 中,使用两个相邻的反斜杠,而不是一个。

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