SSL in WinHTTP

Microsoft Windows HTTP Services (WinHTTP) supporta transazioni SSL (Secure Sockets Layer), inclusi i certificati client. Questo argomento illustra i concetti coinvolti in una transazione SSL e come vengono gestiti tramite WinHTTP.

Secure Sockets Layer

SSL è uno standard stabilito per garantire transazioni HTTP sicure. SSL fornisce un meccanismo per eseguire fino a 128 bit di crittografia in tutte le transazioni tra il client e il server. Consente al client di verificare che il server appartenga a un'entità attendibile tramite l'uso di certificati server. Consente inoltre al server di confermare l'identità del client con i certificati client.

Ognuno di questi problemi di crittografia, identità server e identità client viene negoziato nell'handshake SSL che si verifica quando un client richiede prima una risorsa da un server HTTPS (Secure Hypertext Transfer Protocol). Essenzialmente, il client e il server presentano un elenco di impostazioni obbligatorie e preferite. Se è possibile concordare e soddisfare un set comune di requisiti, viene stabilita una connessione SSL.

WinHTTP offre un'interfaccia di alto livello per l'uso di SSL. Anche se i dettagli dell'handshake SSL e della transazione vengono gestiti internamente, WinHTTP consente di recuperare i livelli di crittografia, specificare il protocollo di sicurezza e interagire con i certificati server e client. Le sezioni seguenti forniscono informazioni dettagliate sulla creazione di applicazioni basate su WinHTTP che scelgono una versione del protocollo SSL, esaminano i certificati server e selezionano i certificati client da inviare ai server HTTPS.

Certificati del server

I certificati server vengono inviati dal server al client in modo che il client possa ottenere una chiave pubblica per il server e assicurarsi che il server sia stato verificato da un'autorità di certificazione. I certificati possono contenere vari tipi di dati. Ad esempio, un certificato X.509 include il formato del certificato, il numero di serie del certificato, l'algoritmo usato per firmare il certificato, il nome dell'autorità di certificazione (CA) che ha emesso il certificato, il nome e la chiave pubblica dell'entità che richiede il certificato e la firma della CA.

Quando si usa l'API (Application Programming Interface) WinHTTP, è possibile recuperare un certificato server chiamando WinHttpQueryOption e specificando il flag WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT . Il certificato del server viene restituito in una struttura WINHTTP_CERTIFICATE_INFO . Se si preferisce recuperare il contesto del certificato, specificare invece il flag WINHTTP_OPTION_SERVER_CERT_CONTEXT .

Se un certificato del server contiene errori, è possibile ottenere informazioni dettagliate sull'errore nella funzione di callback di stato. La notifica WINHTTP_CALLBACK_STATUS_SECURE_FAILURE indica un errore con un certificato del server. Il parametro lpvStatusInformation contiene uno o più flag di errore dettagliati. Per altre informazioni, vedere WINHTTP_STATUS_CALLBACK .

Certificati client

Durante l'handshake SSL, il server potrebbe richiedere l'autenticazione. Il client viene autenticato fornendo un certificato client valido al server. WinHTTP consente di selezionare e inviare un certificato da un archivio certificati locale. Le sezioni seguenti descrivono il processo che fornisce certificati client quando si usa l'API WinHTTP o l'oggetto WinHttpRequest .

WinHTTP API

Sia WinHttpSendRequest che WinHttpReceiveResponse possono non riuscire a indicare che una richiesta non è riuscita perché il server HTTPS richiede l'autenticazione. In questi casi, chiamare GetLastError per restituire ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED. Al momento della ricezione di questo errore, usare le funzioni CryptoAPI appropriate per trovare un certificato appropriato. Indicare che questo certificato deve essere inviato con la richiesta successiva chiamando WinHttpSetOption con il flag WINHTTP_OPTION_CLIENT_CERT_CONTEXT .

Nell'esempio di codice seguente viene illustrato come aprire un archivio certificati e individuare un certificato basato sul nome soggetto dopo la restituzione dell'errore di 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.
      }
    }
  }

Prima di inviare nuovamente una richiesta contenente un certificato client, è possibile determinare se il livello di crittografia supportato è accettabile per l'applicazione. Chiamare WinHttpQueryOption e specificare il flag WINHTTP_OPTION_SECURITY_FLAGS per determinare il livello di crittografia usato.

Recupero elenco autorità di certificazione per l'autenticazione client SSL

Quando l'applicazione client WinHttp invia una richiesta a un server HTTP sicuro che richiede l'autenticazione client SSL, WinHttp restituisce un ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED se l'applicazione non ha fornito un certificato client. Per i computer in esecuzione in Windows Server 2008 e Windows Vista, WinHttp consente all'applicazione di recuperare l'elenco di autorità di certificazione fornito dal server nella richiesta di autenticazione. L'elenco autorità di certificazione specifica un elenco di autorità di certificazione autorizzate dal server per rilasciare certificati client. L'applicazione filtra l'elenco di autorità emittenti per ottenere il certificato richiesto.

L'applicazione client WinHttp recupera l'elenco dell'autorità emittente quando WinHttpSendRequest o WinHttpReceiveResponse restituisceERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED. Quando viene restituito questo errore, l'applicazione chiama WinHttpQueryOption con l'opzione WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST . Il parametro lpBuffer deve essere sufficientemente grande da contenere un puntatore alla struttura SecPkgContext_IssuerListInfoEx . Nell'esempio di codice seguente viene illustrato come recuperare l'elenco di autorità emittenti.

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

Le informazioni nella struttura SecPkgContext_IssuerListInfoEx, cIssuers e aIssuers, possono essere usate per cercare il certificato, come illustrato nell'esempio di codice seguente. Per altre informazioni, vedere 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;
}

Certificati SSL client facoltativi

A partire da Windows Server 2008 e Windows Vista, l'API WinHttp supporta i certificati client facoltativi. Quando il server richiede un certificato client, WinHttpSendRequest o WinHttpRecieveResponse restituisce un errore ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED . Se il server richiede il certificato, ma non lo richiede, l'applicazione può specificare questa opzione per indicare che non dispone di un certificato. Il server può scegliere un altro schema di autenticazione o consentire l'accesso anonimo al server. L'applicazione specifica la macro WINHTTP_NO_CLIENT_CERT_CONTEXT nel parametro lpBuffer di WinHttpSetOption , come illustrato nell'esempio di codice seguente.

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

Se il WINHTTP_NO_CLIENT_CERT_CONTEXT è impostato e il server richiede ancora un certificato client, può inviare un codice di stato HTTP 403. Per altre informazioni, vedere l'opzione WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST .

Oggetto WinHttpRequest

Utilizzare il metodo SetClientCertificate dell'oggetto WinHttpRequest per selezionare i certificati client da inviare al server con una richiesta. Selezionare un certificato specificando una stringa di selezione del certificato con il metodo SetClientCertificate . La stringa di selezione del certificato è costituita dal percorso del certificato, dall'archivio certificati e dal nome del soggetto delimitato da barre rovesciate. Nella tabella seguente sono elencati i componenti per questa stringa di selezione.

Componente Descrizione Valori possibili
Posizione Determina la chiave del Registro di sistema in cui vengono archiviati i certificati. I valori possibili sono "LOCAL_MACHINE" per indicare che l'archivio certificati è in HKEY_LOCAL_MACHINE
e "CURRENT_USER" per indicare che l'archivio certificati si trova nel HKEY_CURRENT_USER non rappresentato.
Questo componente fa distinzione tra maiuscole e minuscole.
Archivio certificati Indica il nome dell'archivio certificati che contiene il certificato pertinente. Gli archivi certificati tipici sono "MY", "Root" e "TrustedPeople". Questo componente fa distinzione tra maiuscole e minuscole.
Nome oggetto Identifica un certificato all'interno dell'archivio certificati specificato. Viene selezionato il primo certificato contenente la stringa specificata per questo componente. Il nome del soggetto può essere qualsiasi stringa. Una stringa vuota indica che deve essere usato il primo certificato nell'archivio certificati . Questo componente non fa distinzione tra maiuscole e minuscole.

Il nome e il percorso dell'archivio certificati sono componenti facoltativi. Tuttavia, se si specifica un archivio certificati, è necessario specificare anche il percorso dell'archivio certificati. Il percorso predefinito è CURRENT_USER e l'archivio certificati predefinito è "MY".

Nell'esempio di codice seguente viene illustrato come specificare che un certificato con l'oggetto "My Middle-Tier Certificate" deve essere scelto dall'archivio certificati "Personale" nel Registro di sistema in HKEY_LOCAL_MACHINE.

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

Nota

In alcune lingue la barra rovesciata è un carattere di escape. Ricordarsi di modificare la stringa di selezione del certificato per tenere conto di questo. In Microsoft JScript, ad esempio, usare due barre rovesciata adiacenti anziché una.

Se non si specifica un certificato e un server HTTPS richiede un certificato client, WinHTTP seleziona il primo certificato nell'archivio certificati predefinito. Se non esistono certificati, viene generato un errore. Se il certificato non viene accettato, il server restituisce un codice di stato 403 per indicare che la richiesta non può essere soddisfatta. È quindi possibile scegliere un certificato più appropriato con SetClientCertificate e inviare di nuovo la richiesta.