2012-05-17 19 views
15

Actualmente estoy implementando un pequeño servidor HTTP usando la versión 2.0 de la API del servidor HTTP de Microsoft (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510(v=vs.85).aspx).API de servidor HTTP de Microsoft: ¿cómo usar SSL para solicitar certificado de cliente?

Necesito habilitar HTTPS en el servidor y también exigir el certificado del cliente cuando las solicitudes del cliente están entrando (necesito que el cliente pueda autenticar el servidor y el servidor para autenticar al cliente y deben comunicarse a través de SSL).

Hasta ahora he podido habilitar el SSL del lado del servidor, así me puedo conectar de forma segura al sitio {https://127.0.0.1:9999/hello}, realizar solicitudes al servidor y recibir respuestas, pero no he podido activar la función que también solicita el certificado del cliente (y lo verifica).

dije en mi código de la aplicación que estoy escuchando "{} https://127.0.0.1:9999/hello" URL (esta era la URL añadí al grupo URL) y luego utilizar la herramienta Netsh.exe para unir el puerto 9999 para SSL:

C:\>netsh http add sslcert ipport=0.0.0.0:9999 certhash=e515b6512e92f4663252eac72c28a784f2d78c6 appid={2C565242-B238-11D3-442D-0008C779D776} clientcertnegotiation=enable 

No estoy seguro de qué debería hacer exactamente "clientcertnegotiation = enable", los documentos indicaron que debería "activar la negociación del certificado". Así que ahora he añadido una llamada a una función adicional a mi servidor HTTP de código:

DWORD answer = 0; 
    HTTP_SSL_CLIENT_CERT_INFO sslClientCertInfo; 
    ULONG bytesReceived; 
    answer = HttpReceiveClientCertificate(hReqQueue, pRequest->ConnectionId, 0, 
     &sslClientCertInfo, sizeof(HTTP_SSL_CLIENT_CERT_INFO), &bytesReceived, NULL); 

entendí que ahora el cliente debe pedir para el certificado, pero no funciona (Probablemente estoy haciendo algo mal, por lo que es la razón por la que estoy escribiendo mi pregunta aquí). El valor de "respuesta" es 1168 (ERROR_NOT_FOUND). Estoy usando el navegador firefox como cliente y he agregado un certificado allí: Herramientas-> Opciones-> Ver certificados-> Importar, por lo que firefox probablemente debería usar ese certificado o solicitud de algún certificado, pero sospecho que no lo hace. 't recibe la solicitud del servidor para el certificado del cliente en absoluto.

¿En qué punto debería el servidor HTTP solicitar el certificado de cliente de todos modos? Pensé que debería ser justo después de la solicitud entrante. Para demostrar qué es exactamente lo que estoy haciendo, estoy usando el código de aplicación de ejemplo HTTP Server de Microsoft (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364640(v=vs.85).aspx), que he modificado sligthly:

#include "precomp.h" 
#include <iostream> 

// 
// Macros. 
// 
#define INITIALIZE_HTTP_RESPONSE(resp, status, reason) \ 
do              \ 
{              \ 
    RtlZeroMemory((resp), sizeof(*(resp)));   \ 
    (resp)->StatusCode = (status);      \ 
    (resp)->pReason = (reason);       \ 
    (resp)->ReasonLength = (USHORT) strlen(reason);  \ 
} while (FALSE) 

#define ADD_KNOWN_HEADER(Response, HeaderId, RawValue)    \ 
do                \ 
{                \ 
    (Response).Headers.KnownHeaders[(HeaderId)].pRawValue =  \ 
                 (RawValue);\ 
    (Response).Headers.KnownHeaders[(HeaderId)].RawValueLength = \ 
     (USHORT) strlen(RawValue);        \ 
} while(FALSE) 

#define ALLOC_MEM(cb) HeapAlloc(GetProcessHeap(), 0, (cb)) 

#define FREE_MEM(ptr) HeapFree(GetProcessHeap(), 0, (ptr)) 

// 
// Prototypes. 
// 
DWORD DoReceiveRequests(HANDLE hReqQueue); 

DWORD SendHttpResponse(HANDLE hReqQueue, PHTTP_REQUEST pRequest, USHORT StatusCode, PSTR pReason, PSTR pEntity); 

DWORD SendHttpPostResponse(HANDLE hReqQueue, PHTTP_REQUEST pRequest); 

/*******************************************************************++ 

Routine Description: 
main routine 

Arguments: 
argc - # of command line arguments. 
argv - Arguments. 

Return Value: 
Success/Failure 

--*******************************************************************/ 
int __cdecl wmain(int argc, wchar_t * argv[]) 
{ 
ULONG   retCode; 
HANDLE   hReqQueue  = NULL; //request queue handle 
int    UrlAdded  = 0; 
HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_2; 


retCode = HttpInitialize( 
      HttpApiVersion, 
      HTTP_INITIALIZE_SERVER , 
      NULL      
      ); 

if (retCode == NO_ERROR) 
{ 
    // If intialize succeeded, create server session 
    HTTP_SERVER_SESSION_ID serverSessionId = NULL; 
    retCode = HttpCreateServerSession(HttpApiVersion, &serverSessionId, 0); 
    if (retCode == NO_ERROR) 
    { 
    // server session creation succeeded 

    //create request queue 
    retCode = HttpCreateRequestQueue(HttpApiVersion, NULL, NULL, 0, &hReqQueue); 
    if (retCode == NO_ERROR) 
    { 
     //create the URL group 
     HTTP_URL_GROUP_ID urlGroupId = NULL; 
     retCode = HttpCreateUrlGroup(serverSessionId, &urlGroupId, 0); 
     if (retCode == NO_ERROR) 
     { 
     retCode = HttpAddUrlToUrlGroup(urlGroupId, L"https://127.0.0.1:9999/hello", 0, 0); 
     if (retCode == NO_ERROR) 
     { 
      //Set url group properties 

      //First let's set the binding property: 
      HTTP_BINDING_INFO bindingInfo; 
      bindingInfo.RequestQueueHandle = hReqQueue; 
      HTTP_PROPERTY_FLAGS propertyFlags; 
      propertyFlags.Present = 1; 
      bindingInfo.Flags = propertyFlags; 
      retCode = HttpSetUrlGroupProperty(
        urlGroupId, 
        HttpServerBindingProperty, 
        &bindingInfo, 
         sizeof(HTTP_BINDING_INFO)); 


      DoReceiveRequests(hReqQueue); 
     } 

     HttpCloseUrlGroup(urlGroupId); 
     }//if HttpCreateUrlGroup succeeded 

     HttpCloseRequestQueue(hReqQueue); 
    }//if HttpCreateRequestQueue succeeded 


    HttpCloseServerSession(serverSessionId);   
    } // if HttpCreateServerSession succeeded 

    HttpTerminate(HTTP_INITIALIZE_SERVER, NULL); 
}// if httpInialize succeeded 

return retCode; 

}//main 


/*******************************************************************++ 

Routine Description: 
The function to receive a request. This function calls the 
corresponding function to handle the response. 

Arguments: 
hReqQueue - Handle to the request queue 

Return Value: 
Success/Failure. 

--*******************************************************************/ 
DWORD DoReceiveRequests(IN HANDLE hReqQueue) 
{ 
ULONG    result; 
HTTP_REQUEST_ID requestId; 
DWORD    bytesRead; 
PHTTP_REQUEST  pRequest; 
PCHAR    pRequestBuffer; 
ULONG    RequestBufferLength; 

// 
// Allocate a 2 KB buffer. This size should work for most 
// requests. The buffer size can be increased if required. Space 
// is also required for an HTTP_REQUEST structure. 
// 
RequestBufferLength = sizeof(HTTP_REQUEST) + 2048; 
pRequestBuffer  = (PCHAR) ALLOC_MEM(RequestBufferLength); 

if (pRequestBuffer == NULL) 
{ 
    return ERROR_NOT_ENOUGH_MEMORY; 
} 

pRequest = (PHTTP_REQUEST)pRequestBuffer; 

// 
// Wait for a new request. This is indicated by a NULL 
// request ID. 
// 

HTTP_SET_NULL_ID(&requestId); 

for(;;) 
{ 
    RtlZeroMemory(pRequest, RequestBufferLength); 

    result = HttpReceiveHttpRequest(
       hReqQueue,   // Req Queue 
       requestId,   // Req ID 
       0,     // Flags 
       pRequest,   // HTTP request buffer 
       RequestBufferLength,// req buffer length 
       &bytesRead,   // bytes received 
       NULL    // LPOVERLAPPED 
       ); 
      if(NO_ERROR == result) 
    { 

     DWORD answer = 0; 
     HTTP_SSL_CLIENT_CERT_INFO sslClientCertInfo; 
     ULONG bytesReceived; 
     answer = HttpReceiveClientCertificate(hReqQueue, pRequest->ConnectionId, 0, 
       &sslClientCertInfo, sizeof(HTTP_SSL_CLIENT_CERT_INFO), &bytesReceived, NULL); 


     if (answer != NO_ERROR) 
     { 
      result = SendHttpResponse(hReqQueue, pRequest, 401, "Unauthorized request", "Unauthorized request"); 
     } 
     else 
     { 
      result = SendHttpResponse(hReqQueue, pRequest, 200, "OK", "OK"); 
     } 

     if (result != NO_ERROR) 
     { 
      break; //if failed to send response, stop listening for further incoming requests 
     } 
     // 
     // Reset the Request ID to handle the next request. 
     // 
     HTTP_SET_NULL_ID(&requestId); 
    } 
    else 
    { 
     break; 
    } 

} 
if(pRequestBuffer) 
{ 
    FREE_MEM(pRequestBuffer); 
} 

return result; 
} 



/*******************************************************************++ 

Routine Description: 
The routine sends a HTTP response 

Arguments: 
hReqQueue  - Handle to the request queue 
pRequest  - The parsed HTTP request 
StatusCode - Response Status Code 
pReason  - Response reason phrase 
pEntityString - Response entity body 

Return Value: 
Success/Failure. 
--*******************************************************************/ 

DWORD SendHttpResponse(
IN HANDLE  hReqQueue, 
IN PHTTP_REQUEST pRequest, 
IN USHORT  StatusCode, 
IN PSTR   pReason, 
IN PSTR   pEntityString 
) 
{ 
HTTP_RESPONSE response; 
HTTP_DATA_CHUNK dataChunk; 
DWORD   result; 
DWORD   bytesSent; 


INITIALIZE_HTTP_RESPONSE(&response, StatusCode, pReason); 
ADD_KNOWN_HEADER(response, HttpHeaderContentType, "text/html"); 


if(pEntityString) 
{ 
    // 
    // Add an entity chunk. 
    // 
    dataChunk.DataChunkType   = HttpDataChunkFromMemory; 
    dataChunk.FromMemory.pBuffer  = pEntityString; 
    dataChunk.FromMemory.BufferLength = 
            (ULONG) strlen(pEntityString); 

    response.EntityChunkCount   = 1; 
    response.pEntityChunks   = &dataChunk; 
} 

result = HttpSendHttpResponse(
       hReqQueue,   // ReqQueueHandle 
       pRequest->RequestId, // Request ID 
       0,     // Flags 
       &response,   // HTTP response 
       NULL,    // pReserved1 
       &bytesSent,   // bytes sent (OPTIONAL) 
       NULL,    // pReserved2 (must be NULL) 
       0,     // Reserved3 (must be 0) 
       NULL,    // LPOVERLAPPED(OPTIONAL) 
       NULL     // pReserved4 (must be NULL) 
       ); 

if(result != NO_ERROR) 
{ 
    wprintf(L"HttpSendHttpResponse failed with %lu \n", result); 
} 

return result; 
} 

Así que mi pregunta es, ¿cómo podría activar la característica de que requiere un certificado de cliente y cómo verifico el certificado una vez que lo he recibido (el código de muestra actual solo trata de recibir el certificado del cliente, falta la parte de verificación)? Realmente no he encontrado ninguna muestra de internet que use la API de Microsoft HTTP Server y requiera certificados de cliente.

Gracias a todos por adelantado.

+0

Hola liismai, ¿ha hecho algún progreso ? Saludos, Manuel – Manuel

Respuesta

1

La estructura HTTP_SERVICE_CONFIG_SSL_PARAM, que se pasa finalmente a HttpSetServiceConfiguration se utiliza para activar la negociación de certificados de cliente (a través de la bandera HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT) y fases de verificación por defecto (vía DefaultCertCheckMode).

Puede recuperar el certificado para realizar una verificación adicional de forma manual llamando al HttpReceiveClientCertificate.

Hay un par de buenos ejemplos, pero la mayoría parece estar llamando desde .net. La configuración se muestra en un ejemplo en el p/Invoke page, eliminación de.neta de cabeza se deja como ejercicio para el lector:

HTTPAPI_VERSION httpApiVersion = new HTTPAPI_VERSION(1, 0); 
retVal = HttpInitialize(httpApiVersion, HTTP_INITIALIZE_CONFIG, IntPtr.Zero); 
if ((uint)NOERROR == retVal) 
{ 
HTTP_SERVICE_CONFIG_SSL_SET configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET(); 
HTTP_SERVICE_CONFIG_SSL_KEY httpServiceConfigSslKey = new HTTP_SERVICE_CONFIG_SSL_KEY(); 
HTTP_SERVICE_CONFIG_SSL_PARAM configSslParam = new HTTP_SERVICE_CONFIG_SSL_PARAM(); 

IPAddress ip = IPAddress.Parse(ipAddress); 

IPEndPoint ipEndPoint = new IPEndPoint(ip, port); 
// serialize the endpoint to a SocketAddress and create an array to hold the values. Pin the array. 
SocketAddress socketAddress = ipEndPoint.Serialize(); 
byte[] socketBytes = new byte[socketAddress.Size]; 
GCHandle handleSocketAddress = GCHandle.Alloc(socketBytes, GCHandleType.Pinned); 
// Should copy the first 16 bytes (the SocketAddress has a 32 byte buffer, the size will only be 16, 
//which is what the SOCKADDR accepts 
for (int i = 0; i < socketAddress.Size; ++i) 
{ 
    socketBytes[i] = socketAddress[i]; 
} 

httpServiceConfigSslKey.pIpPort = handleSocketAddress.AddrOfPinnedObject(); 

GCHandle handleHash = GCHandle.Alloc(hash, GCHandleType.Pinned); 
configSslParam.AppId = Guid.NewGuid(); 
configSslParam.DefaultCertCheckMode = 0; 
configSslParam.DefaultFlags = HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT; 
configSslParam.DefaultRevocationFreshnessTime = 0; 
configSslParam.DefaultRevocationUrlRetrievalTimeout = 0; 
configSslParam.pSslCertStoreName = StoreName.My.ToString(); 
configSslParam.pSslHash = handleHash.AddrOfPinnedObject(); 
configSslParam.SslHashLength = hash.Length; 
configSslSet.ParamDesc = configSslParam; 
configSslSet.KeyDesc = httpServiceConfigSslKey; 

IntPtr pInputConfigInfo = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_SET))); 
Marshal.StructureToPtr(configSslSet, pInputConfigInfo, false); 

retVal = HttpSetServiceConfiguration(IntPtr.Zero, 
    HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, 
    pInputConfigInfo, 
    Marshal.SizeOf(configSslSet), 
    IntPtr.Zero); 

if ((uint)ERROR_ALREADY_EXISTS == retVal) // ERROR_ALREADY_EXISTS = 183 
{ 
    retVal = HttpDeleteServiceConfiguration(IntPtr.Zero, 
    HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, 
    pInputConfigInfo, 
    Marshal.SizeOf(configSslSet), 
    IntPtr.Zero); 

    if ((uint)NOERROR == retVal) 
    { 
    retVal = HttpSetServiceConfiguration(IntPtr.Zero, 
     HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, 
     pInputConfigInfo, 
     Marshal.SizeOf(configSslSet), 
     IntPtr.Zero); 
    } 
} 

Hay una separate pastebin que utiliza netsh para la configuración, pero no acceder al certificado recibido:

for(;;) 
{ 
    RtlZeroMemory(pRequest, RequestBufferLength); 

    result = HttpReceiveHttpRequest(
       hReqQueue,   // Req Queue 
       requestId,   // Req ID 
       0,     // Flags 
       pRequest,   // HTTP request buffer 
       RequestBufferLength,// req buffer length 
       &bytesRead,   // bytes received 
       NULL    // LPOVERLAPPED 
       ); 
      if(NO_ERROR == result) 
    { 

     DWORD answer = 0; 
     HTTP_SSL_CLIENT_CERT_INFO sslClientCertInfo; 
     ULONG bytesReceived; 
     answer = HttpReceiveClientCertificate(hReqQueue, pRequest->ConnectionId, 0, 
       &sslClientCertInfo, sizeof(HTTP_SSL_CLIENT_CERT_INFO), &bytesReceived, NULL); 


     if (answer != NO_ERROR) 
     { 
      result = SendHttpResponse(hReqQueue, pRequest, 401, "Unauthorized request", "Unauthorized request"); 
     } 
     else 
     { 
      result = SendHttpResponse(hReqQueue, pRequest, 200, "OK", "OK"); 
     } 

     if (result != NO_ERROR) 
     { 
      break; //if failed to send response, stop listening for further incoming requests 
     } 
     // 
     // Reset the Request ID to handle the next request. 
     // 
     HTTP_SET_NULL_ID(&requestId); 
    } 
    else 
    { 
     break; 
    } 

} 
Cuestiones relacionadas