WinHTTP 中的 SSL

Microsoft Windows HTTP Services (WinHTTP) 支援安全通訊端層 (SSL) 交易,包括用戶端憑證。 本主題說明 SSL 交易所涉及的概念,以及如何使用 WinHTTP 來處理這些概念。

安全通訊端層

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) 清單。 應用程式會篩選簽發者清單,以取得必要的憑證。

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結構、cIssuers 和 aIssuers中的資訊可用來搜尋憑證,如下列程式碼範例所示。 如需詳細資訊,請參閱 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」。

下列程式碼範例示範如何指定主體為 「我的Middle-Tier憑證」的憑證,應該從登錄的 [個人] 憑證存放區 中選擇 HKEY_LOCAL_MACHINE

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

注意

在某些語言中,反斜線是逸出字元。 請記得修改憑證選取字串以考慮此專案。 例如,在 Microsoft JScript 中,使用兩個相鄰反斜線,而不是一個。

如果您未指定憑證,且 HTTPS 伺服器需要用戶端憑證,WinHTTP 會選取預設 憑證存放區中的第一個憑證。 如果沒有憑證存在,就會引發錯誤。 如果不接受憑證,伺服器會傳回 403 狀態碼,指出無法完成要求。 接著,您可以使用 SetClientCertificate 選擇更適當的憑證,然後重新傳送要求。