c++ windows client-certificates

c++ - Microsoft HTTP Server API: usando SSL, ¿cómo exigir el certificado del cliente?



windows client-certificates (1)

Actualmente estoy implementando un pequeño servidor HTTP utilizando 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 ).

Necesito habilitar HTTPS en el lado del servidor y también exigir el certificado del cliente cuando se reciben solicitudes (necesito que el cliente pueda autenticar el servidor y el servidor para autenticar al cliente y que se comuniquen a través de SSL).

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

Dije en mi código de aplicación que escuchaba la URL "{ https://127.0.0.1:9999/hello }" (esta fue la URL que agregué al grupo de URL) y luego usé la herramienta netsh.exe para enlazar el 9999 puerto a 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 lo que debería hacer exactamente con "clientcertnegotiation = enable", los documentos dijeron que debería "activar la negociación del certificado". Así que ahora agregué una llamada de función adicional a mi código de servidor HTTP:

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 );

Comprendí que ahora se debe solicitar el certificado al cliente, pero no funciona (probablemente estoy haciendo algo mal, por lo que esa es la razón por la que escribo 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 probablemente Firefox debería usar ese certificado o solicitud de algún certificado, pero sospecho que Firefox no funciona. No reciba la solicitud del servidor para el certificado de 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 la Aplicación de ejemplo del servidor HTTP de Microsoft ( http://msdn.microsoft.com/en-us/library/windows/desktop/aa364640(v=vs.85).aspx ), que he modificado ligeramente:

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

Entonces, mi pregunta es, ¿cómo podría habilitar la función que requiere el certificado del cliente y cómo verifico el certificado una vez que lo he recibido (el código de muestra actual solo intenta recibir el certificado del cliente, falta la parte de verificación)? Realmente no he encontrado ninguna muestra de Internet que use la API del servidor HTTP de Microsoft y requiera certificados de cliente.

Gracias a todos ya por adelantado.


La estructura HTTP_SERVICE_CONFIG_SSL_PARAM , que se pasa finalmente a HttpSetServiceConfiguration se usa para habilitar la negociación de certificados de cliente (a través del indicador HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT ) y los pasos de verificación predeterminados (a través de DefaultCertCheckMode ).

Puede recuperar el certificado para realizar una verificación adicional manualmente llamando a 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 la página p / Invoke , la eliminación de la sobrecarga de .net se deja como un 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 un pastebin separado que usa netsh para hacer la configuración, pero accede 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; } }