SSL en WinHTTP

Los servicios HTTP de Microsoft Windows (WinHTTP) admiten transacciones de Capa de sockets seguros (SSL), incluidos los certificados de cliente. En este tema se explican los conceptos implicados en una transacción SSL y cómo se controlan mediante WinHTTP.

Capa de sockets seguros

SSL es un estándar establecido para garantizar transacciones HTTP seguras. SSL proporciona un mecanismo para realizar un cifrado de hasta 128 bits en todas las transacciones entre el cliente y el servidor. Permite al cliente comprobar que el servidor pertenece a una entidad de confianza mediante el uso de certificados de servidor. También permite al servidor confirmar la identidad del cliente con certificados de cliente.

Cada uno de estos problemas, el cifrado, la identidad del servidor y la identidad de cliente se negocian en el protocolo de enlace SSL que se produce cuando un cliente solicita por primera vez un recurso desde un servidor de protocolo de transferencia de hipertexto seguro (HTTPS). Básicamente, el cliente y el servidor presentan una lista de la configuración requerida y preferida. Si se puede acordar y cumplir un conjunto común de requisitos, se establece una conexión SSL.

WinHTTP proporciona una interfaz de alto nivel para usar SSL. Aunque los detalles del protocolo de enlace SSL y la transacción se controlan internamente, WinHTTP permite recuperar los niveles de cifrado, especificar el protocolo de seguridad e interactuar con los certificados de servidor y cliente. En las secciones siguientes se proporcionan detalles sobre la creación de aplicaciones basadas en WinHTTP que eligen una versión del protocolo SSL, examinan los certificados de servidor y seleccionan los certificados de cliente para enviarlos a los servidores HTTPS.

Certificados de servidor

Los certificados de servidor se envían desde el servidor al cliente para que el cliente pueda obtener una clave pública para el servidor y asegurarse de que el servidor ha sido comprobado por una entidad de certificación. Los certificados pueden contener tipos diferentes de datos. Por ejemplo, un certificado X.509 incluye el formato del certificado, el número de serie del certificado, el algoritmo utilizado para firmar el certificado, el nombre de la entidad de certificación (CA) que emitió el certificado, el nombre y la clave pública de la entidad que solicita el certificado y la firma de la entidad de certificación.

Al usar la interfaz de programación de aplicaciones (API) winHTTP, puede recuperar un certificado de servidor llamando a WinHttpQueryOption y especificando la marca WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT . El certificado de servidor se devuelve en una estructura de WINHTTP_CERTIFICATE_INFO . Si prefiere recuperar el contexto del certificado, especifique la marca WINHTTP_OPTION_SERVER_CERT_CONTEXT en su lugar.

Si un certificado de servidor contiene errores, se pueden obtener detalles sobre el error en la función de devolución de llamada de estado. La notificación WINHTTP_CALLBACK_STATUS_SECURE_FAILURE indica un error con un certificado de servidor. El parámetro lpvStatusInformation contiene una o varias marcas de error detalladas. Consulte WINHTTP_STATUS_CALLBACK para obtener más información.

Certificados de cliente

Durante el protocolo de enlace SSL, es posible que el servidor requiera autenticación. El cliente se autentica proporcionando un certificado de cliente válido al servidor. WinHTTP permite seleccionar y enviar un certificado desde un almacén de certificados local. En las secciones siguientes se describe el proceso que proporciona certificados de cliente al usar la API winHTTP o el objeto WinHttpRequest .

WinHTTP API

WinHttpSendRequest y WinHttpReceiveResponse no pueden indicar que una solicitud no se realizó correctamente porque el servidor HTTPS requiere autenticación. En estos casos, llame a GetLastError para devolver ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED. Al recibir este error, use las funciones CryptoAPI adecuadas para buscar un certificado adecuado. Indique que este certificado se debe enviar con la siguiente solicitud llamando a WinHttpSetOption con la marca WINHTTP_OPTION_CLIENT_CERT_CONTEXT .

En el ejemplo de código siguiente se muestra cómo abrir un almacén de certificados y buscar un certificado basado en el nombre del firmante después de que se haya devuelto el error 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.
      }
    }
  }

Antes de volver a enviar una solicitud que contiene un certificado de cliente, puede determinar si el nivel de cifrado admitido es aceptable para la aplicación. Llame a WinHttpQueryOption y especifique la marca WINHTTP_OPTION_SECURITY_FLAGS para determinar el nivel de cifrado que se usa.

Recuperación de lista de emisores para la autenticación de cliente SSL

Cuando la aplicación cliente winHttp envía una solicitud a un servidor HTTP seguro que requiere autenticación de cliente SSL, WinHttp devuelve un ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED si la aplicación no ha proporcionado un certificado de cliente. En el caso de los equipos que se ejecutan en Windows Server 2008 y Windows Vista, WinHttp permite a la aplicación recuperar la lista de emisores de certificados proporcionada por el servidor en el desafío de autenticación. La lista de emisores especifica una lista de entidades de certificación (CA) autorizadas por el servidor para emitir certificados de cliente. La aplicación filtra la lista de emisores para obtener el certificado necesario.

La aplicación cliente WinHttp recupera la lista de emisores cuando WinHttpSendRequest o WinHttpReceiveResponse devuelve ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED. Cuando se devuelve este error, la aplicación llama a WinHttpQueryOption con la opción WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST . El parámetro lpBuffer debe ser lo suficientemente grande como para contener un puntero a la estructura SecPkgContext_IssuerListInfoEx . En el ejemplo de código siguiente se muestra cómo recuperar la lista de emisores.

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

La información de la estructura SecPkgContext_IssuerListInfoEx , cIssuers y aIssuers, se puede usar para buscar el certificado, como se muestra en el ejemplo de código siguiente. Para obtener más información, vea 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 de cliente opcionales

A partir de Windows Server 2008 y Windows Vista, la API winHttp admite certificados de cliente opcionales. Cuando el servidor solicita un certificado de cliente, WinHttpSendRequest o WinHttpRecieveResponse devuelve un error ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED . Si el servidor solicita el certificado, pero no lo requiere, la aplicación puede especificar esta opción para indicar que no tiene un certificado. El servidor puede elegir otro esquema de autenticación o permitir el acceso anónimo al servidor. La aplicación especifica la macro WINHTTP_NO_CLIENT_CERT_CONTEXT en el parámetro lpBuffer de WinHttpSetOption , como se muestra en el ejemplo de código siguiente.

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

Si se establece el WINHTTP_NO_CLIENT_CERT_CONTEXT y el servidor sigue necesitando un certificado de cliente, puede enviar un código de estado HTTP 403. Para obtener más información, consulte la opción WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST .

WinHttpRequest (objeto)

Use el método SetClientCertificate del objeto WinHttpRequest para seleccionar los certificados de cliente que se van a enviar al servidor con una solicitud. Seleccione un certificado especificando una cadena de selección de certificado con el método SetClientCertificate . La cadena de selección del certificado consta de la ubicación del certificado, el almacén de certificados y el nombre del firmante delimitados por barras diagonales inversas. En la tabla siguiente se enumeran los componentes de esta cadena de selección.

Componente Descripción Valores posibles
Location Determina la clave del Registro con la que se almacenan los certificados. Los valores posibles son "LOCAL_MACHINE" para indicar que el almacén de certificados está en HKEY_LOCAL_MACHINE
y "CURRENT_USER" para indicar que el almacén de certificados está bajo la HKEY_CURRENT_USER no suplantada.
Este componente distingue mayúsculas de minúsculas.
Almacén de certificados Indica el nombre del almacén de certificados que contiene el certificado correspondiente. Los almacenes de certificados típicos son "MY", "Root" y "TrustedPeople". Este componente distingue mayúsculas de minúsculas.
Nombre de sujeto Identifica un certificado dentro del almacén de certificados especificado. Se selecciona el primer certificado que contiene la cadena especificada para este componente. El nombre del firmante puede ser cualquier cadena. Una cadena en blanco indica que se debe usar el primer certificado del almacén de certificados . Este componente no distingue mayúsculas de minúsculas.

El nombre y la ubicación del almacén de certificados son componentes opcionales. Sin embargo, si especifica un almacén de certificados, también debe especificar la ubicación de ese almacén de certificados. La ubicación predeterminada es CURRENT_USER y el almacén de certificados predeterminado es "MY".

En el ejemplo de código siguiente se muestra cómo especificar que se debe elegir un certificado con el asunto "Mi certificado de Middle-Tier" en el almacén de certificados "Personal" del Registro en HKEY_LOCAL_MACHINE.

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

Nota

En algunos idiomas, la barra diagonal inversa es un carácter de escape. No olvide modificar la cadena de selección de certificados para tener en cuenta esto. Por ejemplo, en Microsoft JScript, use dos barras diagonales inversas adyacentes en lugar de una.

Si no especifica un certificado y un servidor HTTPS requiere un certificado de cliente, WinHTTP selecciona el primer certificado del almacén de certificados predeterminado. Si no existe ningún certificado, se genera un error. Si no se acepta el certificado, el servidor devuelve un código de estado 403 para indicar que no se puede cumplir la solicitud. Después, puede elegir un certificado más adecuado con SetClientCertificate y volver a enviar la solicitud.