Compartilhar via


SSL no WinHTTP

O WinHTTP (Microsoft Windows HTTP Services) dá suporte a transações SSL (Secure Sockets Layer), incluindo certificados de cliente. Este tópico explica os conceitos envolvidos em uma transação SSL e como eles são tratados usando WinHTTP.

protocolo SSL

O SSL é um padrão estabelecido para garantir transações HTTP seguras. O SSL fornece um mecanismo para executar criptografia de até 128 bits em todas as transações entre o cliente e o servidor. Ele permite que o cliente verifique se o servidor pertence a uma entidade confiável por meio do uso de certificados de servidor. Ele também permite que o servidor confirme a identidade do cliente com certificados de cliente.

Cada um desses problemas de criptografia, identidade do servidor e identidade do cliente são negociados no handshake SSL que ocorre quando um cliente solicita pela primeira vez um recurso de um servidor HTTPS (Secure Hypertext Transfer Protocol). Essencialmente, o cliente e o servidor apresentam uma lista de configurações obrigatórias e preferenciais. Se um conjunto comum de requisitos puder ser acordado e atendido, uma conexão SSL será estabelecida.

O WinHTTP fornece uma interface de alto nível para usar o SSL. Embora os detalhes do handshake e da transação SSL sejam tratados internamente, o WinHTTP permite recuperar níveis de criptografia, especificar o protocolo de segurança e interagir com certificados de servidor e cliente. As seções a seguir fornecem detalhes sobre como criar aplicativos baseados em WinHTTP que elegem uma versão do protocolo SSL, examinam certificados de servidor e selecionam certificados de cliente para enviar para servidores HTTPS.

Certificados do servidor

Os certificados de servidor são enviados do servidor para o cliente para que o cliente possa obter uma chave pública para o servidor e garantir que o servidor tenha sido verificado por uma autoridade de certificação. Os certificados podem conter diferentes tipos de dados. Por exemplo, um certificado X.509 inclui o formato do certificado, o número de série do certificado, o algoritmo usado para assinar o certificado, o nome da AC (autoridade de certificação) que emitiu o certificado, o nome e a chave pública da entidade que solicita o certificado e a assinatura da AC.

Ao usar a API (interface de programação de aplicativo) WinHTTP, você pode recuperar um certificado de servidor chamando WinHttpQueryOption e especificando o sinalizador WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT . O certificado do servidor é retornado em uma estrutura WINHTTP_CERTIFICATE_INFO . Se preferir recuperar o contexto do certificado, especifique o sinalizador WINHTTP_OPTION_SERVER_CERT_CONTEXT .

Se um certificado de servidor contiver erros, os detalhes sobre o erro poderão ser obtidos na função de retorno de chamada status. A notificação WINHTTP_CALLBACK_STATUS_SECURE_FAILURE indica um erro com um certificado de servidor. O parâmetro lpvStatusInformation contém um ou mais sinalizadores de erro detalhados. Consulte WINHTTP_STATUS_CALLBACK para obter mais informações.

Certificados do cliente

Durante o handshake SSL, o servidor pode exigir autenticação. O cliente é autenticado fornecendo um certificado de cliente válido para o servidor. O WinHTTP permite que você selecione e envie um certificado de um repositório de certificados local. As seções a seguir descrevem o processo que fornece certificados de cliente ao usar a API WinHTTP ou o objeto WinHttpRequest .

WinHTTP API

WinHttpSendRequest e WinHttpReceiveResponse podem falhar ao indicar que uma solicitação não foi bem-sucedida porque o servidor HTTPS requer autenticação. Nesses casos, chame GetLastError para retornar ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED. Ao receber esse erro, use as funções de CryptoAPI apropriadas para encontrar um certificado apropriado. Indique que esse certificado deve ser enviado com a próxima solicitação chamando WinHttpSetOption com o sinalizador WINHTTP_OPTION_CLIENT_CERT_CONTEXT .

O exemplo de código a seguir mostra como abrir um repositório de certificados e localizar um certificado com base no nome da entidade após o erro ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED ter sido retornado.

  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.
      }
    }
  }

Antes de reenviar uma solicitação que contém um certificado do cliente, você pode determinar se o nível de criptografia com suporte é aceitável para seu aplicativo. Chame WinHttpQueryOption e especifique o sinalizador WINHTTP_OPTION_SECURITY_FLAGS para determinar o nível de criptografia usado.

Recuperação de lista de emissores para autenticação de cliente SSL

Quando o aplicativo cliente WinHttp envia uma solicitação para um servidor HTTP seguro que requer autenticação de cliente SSL, o WinHttp retorna um ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED se o aplicativo não forneceu um certificado de cliente. Para computadores em execução no Windows Server 2008 e no Windows Vista, o WinHttp permite que o aplicativo recupere a lista de emissores de certificados fornecida pelo servidor no desafio de autenticação. A Lista de Emissores especifica uma lista de autoridades de certificação (ACs) autorizadas pelo servidor a emitir certificados de cliente. O aplicativo filtra a lista de emissores para obter o certificado necessário.

O aplicativo cliente WinHttp recupera a lista de emissores quando WinHttpSendRequest ou WinHttpReceiveResponse retorna ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED. Quando esse erro é retornado, o aplicativo chama WinHttpQueryOption com a opção WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST . O parâmetro lpBuffer deve ser grande o suficiente para conter um ponteiro para a estrutura SecPkgContext_IssuerListInfoEx . O exemplo de código a seguir mostra como recuperar a lista de emissores.

#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.
  }
}

As informações na estrutura SecPkgContext_IssuerListInfoEx , cIssuers e aIssuers podem ser usadas para pesquisar o certificado, conforme mostrado no exemplo de código abaixo. Para obter mais informações, consulte 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;
}

Certificados SSL do cliente opcionais

A partir do Windows Server 2008 e do Windows Vista, a API WinHttp dá suporte a certificados de cliente opcionais. Quando o servidor solicita um certificado de cliente, WinHttpSendRequest ou WinHttpRecieveResponse retorna um erro ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED . Se o servidor solicitar o certificado, mas não o exigir, o aplicativo poderá especificar essa opção para indicar que ele não tem um certificado. O servidor pode escolher outro esquema de autenticação ou permitir acesso anônimo ao servidor. O aplicativo especifica a macro WINHTTP_NO_CLIENT_CERT_CONTEXT no parâmetro lpBuffer de WinHttpSetOption , conforme mostrado no exemplo de código a seguir.

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

Se o WINHTTP_NO_CLIENT_CERT_CONTEXT estiver definido e o servidor ainda exigir um certificado do cliente, ele poderá enviar um código de status HTTP 403. Para obter mais informações, consulte a opção WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST .

Objeto WinHttpRequest

Use o método SetClientCertificate do objeto WinHttpRequest para selecionar certificados de cliente para enviar ao servidor com uma solicitação. Selecione um certificado especificando uma cadeia de caracteres de seleção de certificado com o método SetClientCertificate . A cadeia de caracteres de seleção de certificado consiste no local do certificado, no repositório de certificados e no nome da entidade delimitados por barras invertidas. A tabela a seguir lista os componentes dessa cadeia de caracteres de seleção.

Componente Descrição Valores possíveis
Location Determina a chave do Registro sob a qual os certificados são armazenados. Os valores possíveis são "LOCAL_MACHINE" para indicar que o repositório de certificados está em HKEY_LOCAL_MACHINE
e "CURRENT_USER" para indicar que o repositório de certificados está no HKEY_CURRENT_USER não representado.
Esse componente diferencia maiúsculas de minúsculas.
Repositório de certificados Indica o nome do repositório de certificados que contém o certificado relevante. Os repositórios de certificados típicos são "MY", "Root" e "TrustedPeople". Esse componente diferencia maiúsculas de minúsculas.
Nome da entidade Identifica um certificado dentro do repositório de certificados especificado. O primeiro certificado que contém a cadeia de caracteres especificada para esse componente é selecionado. O nome da entidade pode ser qualquer cadeia de caracteres. Uma cadeia de caracteres em branco indica que o primeiro certificado no repositório de certificados deve ser usado. Esse componente não diferencia maiúsculas de minúsculas.

O nome e o local do repositório de certificados são componentes opcionais. No entanto, se você especificar um repositório de certificados, também deverá especificar o local desse repositório de certificados. O local padrão é CURRENT_USER e o repositório de certificados padrão é "MY".

O exemplo de código a seguir mostra como especificar que um certificado com a entidade "Meu Certificado Middle-Tier" deve ser escolhido no repositório de certificados "Pessoal" no registro em HKEY_LOCAL_MACHINE.

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

Observação

Em alguns idiomas, a barra invertida é um caractere de escape. Lembre-se de modificar a cadeia de caracteres de seleção de certificado para considerar isso. Por exemplo, no Microsoft JScript, use duas barras invertidas adjacentes em vez de uma.

Se você não especificar um certificado e um servidor HTTPS exigir um certificado do cliente, o WinHTTP selecionará o primeiro certificado no repositório de certificados padrão. Se nenhum certificado existir, um erro será gerado. Se o certificado não for aceito, o servidor retornará um código 403 status para indicar que a solicitação não pode ser atendida. Em seguida, você pode escolher um certificado mais apropriado com SetClientCertificate e reenviar a solicitação.