Поделиться через


SSL в WinHTTP

Службы HTTP Microsoft Windows (WinHTTP) поддерживают транзакции SSL, включая сертификаты клиента. В этом разделе описываются основные понятия, связанные с транзакцией SSL, и способы их обработки с помощью WinHTTP.

Протокол SSL

SSL — это установленный стандарт для обеспечения безопасности транзакций HTTP. SSL предоставляет механизм для выполнения до 128-разрядного шифрования всех транзакций между клиентом и сервером. Это позволяет клиенту проверить, принадлежит ли сервер доверенной сущности с помощью сертификатов сервера. Это также позволяет серверу подтвердить удостоверение клиента с помощью сертификатов клиента.

Каждая из этих проблем шифрования, удостоверения сервера и удостоверения клиента согласовываются в подтверждении SSL, которое возникает, когда клиент впервые запрашивает ресурс с сервера HTTPS. По сути, каждый клиент и сервер представляют список обязательных и предпочтительных параметров. Если можно согласовать и выполнить общий набор требований, устанавливается SSL-подключение.

WinHTTP предоставляет высокоуровневый интерфейс для использования SSL. Хотя сведения о подтверждении SSL и транзакции обрабатываются внутри организации, WinHTTP позволяет получать уровни шифрования, указывать протокол безопасности и взаимодействовать с сертификатами сервера и клиента. В следующих разделах содержатся сведения о создании приложений на основе WinHTTP, которые выбирают версию протокола SSL, проверяют сертификаты сервера и выбирают клиентские сертификаты для отправки на HTTPS-серверы.

Сертификаты серверов

Сертификаты сервера отправляются с сервера клиенту, чтобы клиент смог получить открытый ключ для сервера и убедиться, что сервер был проверен центром сертификации. В сертификатах могут содержаться различные типы данных. Например, сертификат X.509 включает формат сертификата, серийный номер сертификата, алгоритм, используемый для подписи сертификата, имя центра сертификации (ЦС), выдавшего сертификат, имя и открытый ключ сущности, запрашивающей сертификат, и подпись ЦС.

При использовании интерфейса api программирования приложений WinHTTP можно получить сертификат сервера, вызвав 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 позволяет выбирать и отправлять сертификат из локального хранилища сертификатов. В следующих разделах описывается процесс, предоставляющий сертификаты клиента при использовании API WinHTTP или объекта WinHttpRequest .

WinHTTP API

WinHttpSendRequest и WinHttpReceiveResponse могут не указать, что запрос был неудачным, так как HTTPS-сервер требует проверки подлинности. В таких случаях вызовите Метод GetLastError , чтобы вернуть ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED. Получив эту ошибку, используйте соответствующие функции CryptoAPI , чтобы найти соответствующий сертификат. Укажите, что этот сертификат следует отправить со следующим запросом, вызвав WinHttpSetOption с флагом WINHTTP_OPTION_CLIENT_CERT_CONTEXT .

В следующем примере кода показано, как открыть хранилище сертификатов и найти сертификат на основе имени субъекта после возврата ошибки 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 отправляет запрос на защищенный HTTP-сервер, требующий проверки подлинности клиента SSL, WinHttp возвращает ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED , если приложение не предоставило сертификат клиента. Для компьютеров под управлением Windows Server 2008 и Windows Vista WinHttp позволяет приложению получить список издателей сертификатов, предоставленный сервером в запросе проверки подлинности. Список издателей указывает список центров сертификации (ЦС), авторизованных сервером для выдачи сертификатов клиента. Приложение фильтрует список издателей, чтобы получить необходимый сертификат.

Клиентское приложение WinHttp получает список издателей, когда WinHttpSendRequest или WinHttpReceiveResponse возвращает ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED. При возврате этой ошибки приложение вызывает WinHttpQueryOption с параметром WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST . Параметр 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_IssuerListInfoExcIssuers и 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 API WinHttp поддерживает необязательные сертификаты клиента. Когда сервер запрашивает сертификат клиента, WinHttpSendRequest или WinHttpRecieveResponse возвращает ошибку ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED . Если сервер запрашивает сертификат, но не требует его, приложение может указать этот параметр, чтобы указать, что у него нет сертификата. Сервер может выбрать другую схему проверки подлинности или разрешить анонимный доступ к серверу. Приложение задает макрос WINHTTP_NO_CLIENT_CERT_CONTEXT в параметре lpBufferwinHttpSetOption , как показано в следующем примере кода.

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

Если WINHTTP_NO_CLIENT_CERT_CONTEXT задан, а серверу по-прежнему требуется сертификат клиента, он может отправить код состояния HTTP 403. Дополнительные сведения см. в разделе параметр WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST .

Объект WinHttpRequest

Используйте метод SetClientCertificate объекта WinHttpRequest , чтобы выбрать сертификаты клиента для отправки на сервер с запросом. Выберите сертификат, указав строку выбора сертификата с помощью метода 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")

Примечание

В некоторых языках обратная косая черта является escape-символом. Не забудьте изменить строку выбора сертификата, чтобы учесть это. Например, в Microsoft JScript используйте две смежные обратные косые косые знаки вместо одного.

Если сертификат не указан, а для HTTPS-сервера требуется сертификат клиента, WinHTTP выбирает первый сертификат в хранилище сертификатов по умолчанию. Если сертификатов не существует, возникает ошибка. Если сертификат не принят, сервер возвращает код состояния 403, указывающий, что запрос не может быть выполнен. Затем можно выбрать более подходящий сертификат с помощью SetClientCertificate и повторно отправить запрос.