Win での SSLHTTP

Microsoft Windows HTTP Services (WinHTTP) では、クライアント証明書を含む Secure Sockets Layer (SSL) トランザクションがサポートされています。 このトピックでは、SSL トランザクションに関連する概念と、WinHTTP を使用した SSL トランザクションの処理方法について説明します。

セキュア ソケット レイヤー (Secure Sockets Layer)

SSL は、セキュリティで保護された HTTP トランザクションを確保するための確立された標準です。 SSL は、クライアントとサーバー間のすべてのトランザクションで最大 128 ビット暗号化を実行するメカニズムを提供します。 これにより、クライアントは、サーバー証明書を使用して、サーバーが信頼されたエンティティに属していることを確認できます。 また、サーバーはクライアント証明書を使用してクライアントの ID を確認することもできます。

これらの各問題の暗号化、サーバー ID、およびクライアント ID は、クライアントが最初に Secure Hypertext Transfer Protocol (HTTPS) サーバーからリソースを要求したときに発生する SSL ハンドシェイクでネゴシエートされます。 基本的に、クライアントとサーバーはそれぞれ、必要な設定と優先される設定の一覧を表示します。 共通の要件セットを合意して満たすことができる場合は、SSL 接続が確立されます。

WinHTTP には、SSL を使用するための高度なインターフェイスが用意されています。 SSL ハンドシェイクとトランザクションの詳細は内部的に処理されますが、WinHTTP を使用すると、暗号化レベルを取得し、セキュリティ プロトコルを指定し、サーバー証明書とクライアント証明書を操作できます。 以降のセクションでは、SSL プロトコルのバージョンを選択し、サーバー証明書を調べて、HTTPS サーバーに送信するクライアント証明書を選択する WinHTTP ベースのアプリケーションの作成について詳しく説明します。

サーバー証明書

サーバー証明書はサーバーからクライアントに送信されるため、クライアントはサーバーの公開キーを取得し、サーバーが証明機関によって検証されていることを確認できます。 証明書には、さまざまな種類のデータを含めることができます。 たとえば、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 パラメーターには、1 つ以上の詳細なエラー フラグが含まれています。 詳細については、「 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 クライアント アプリケーションは、 WinHttpSendRequest、または WinHttpReceiveResponseERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDEDを返したときに発行者リストを取得します。 このエラーが返されると、アプリケーションは WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST オプションを使用して WinHttpQueryOption を呼び出します。 lpBuffer パラメーターは、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 ではオプションのクライアント証明書がサポートされています。 サーバーがクライアント証明書、 WinHttpSendRequest、または WinHttpRecieveResponse を要求すると、 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 メソッドを使用して証明書の選択文字列を指定して、証明書を選択します。 証明書の選択文字列は、証明書の場所、 証明書ストア、およびバックスラッシュで区切られたサブジェクト名で構成されます。 次の表に、この選択文字列のコンポーネントを示します。

コンポーネント 説明 設定可能な値
Location 証明書を格納するレジストリ キーを決定します。 使用できる値は、 証明書ストア が HKEY_LOCAL_MACHINE の下にあることを示す "LOCAL_MACHINE" です
証明書 ストア が偽装されていないHKEY_CURRENT_USERの下にあることを示す "CURRENT_USER" 。
このコンポーネントでは、大文字と小文字が区別されます。
証明書ストア 関連する証明書を含む 証明書ストア の名前を示します。 一般的な 証明書ストア は、"MY"、"Root"、および "TrustedPeople" です。 このコンポーネントでは、大文字と小文字が区別されます。
サブジェクト名 指定した証明書ストア内の証明書を識別 します。 このコンポーネントに指定された文字列を含む最初の証明書が選択されます。 サブジェクト名には任意の文字列を指定できます。 空の文字列は、 証明書ストア 内の最初の証明書を使用する必要があることを示します。 このコンポーネントでは、大文字と小文字は区別されません。

証明書ストアの名前と場所は省略可能なコンポーネントです。 ただし、 証明書ストアを指定する場合は、その 証明書ストアの場所も指定する必要があります。 既定の場所はCURRENT_USERで、既定の 証明書ストア は "MY" です。

次のコード例は、"My Middle-Tier Certificate" というサブジェクトを持つ証明書を、レジストリの "個人用" 証明書ストア から選択する必要があることをHKEY_LOCAL_MACHINEに指定する方法 示しています。

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

Note

一部の言語では、円記号はエスケープ文字です。 これを考慮するように証明書の選択文字列を必ず変更してください。 たとえば、Microsoft JScript では、1 つではなく 2 つの隣接する円記号を使用します。

証明書を指定せず、HTTPS サーバーにクライアント証明書が必要な場合、WinHTTP は既定の 証明書ストアの最初の証明書を選択します。 証明書が存在しない場合は、エラーが発生します。 証明書が受け入れられない場合、サーバーは要求を満たすことができないことを示す 403 状態コードを返します。 その後、 SetClientCertificate を使用して、より適切な証明書を選択し、要求を再送信できます。