2012-05-17 43 views
15

Tôi hiện đang triển khai một máy chủ HTTP nhỏ sử dụng Microsoft HTTP Server API Phiên bản 2.0 (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510(v=vs.85).aspx).Microsoft HTTP Server API - sử dụng SSL, cách yêu cầu chứng chỉ ứng dụng khách?

Tôi cần bật HTTPS ở phía máy chủ và cũng yêu cầu chứng chỉ ứng dụng khách khi yêu cầu của khách hàng đến (tôi cần khách hàng có thể xác thực máy chủ và máy chủ để xác thực máy khách và họ nên liên lạc qua SSL).

Cho đến nay tôi đã có thể bật SSL phía máy chủ, vì vậy tôi có thể kết nối an toàn với trang web {https://127.0.0.1:9999/hello}, thực hiện yêu cầu tới máy chủ và nhận phản hồi nhưng tôi chưa thể bật tính năng này cũng yêu cầu chứng chỉ ứng dụng khách (và xác minh nó).

Tôi đã nói trong mã ứng dụng rằng tôi đang nghe "{https://127.0.0.1:9999/hello}" URL (đây là URL tôi đã thêm vào nhóm URL) và sau đó tôi sử dụng công cụ netsh.exe để kết buộc cổng 9999 với SSL:

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

Tôi không chắc chắn điều này "clientcertnegotiation = enable" nên chính xác làm, các tài liệu cho biết cần "bật đàm phán chứng chỉ". Vì vậy, bây giờ tôi thêm một chức năng bổ sung gọi mã máy chủ HTTP của tôi:

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

tôi hiểu rằng bây giờ là khách hàng nên được nhắc nhở cấp giấy chứng nhận, nhưng nó không hoạt động (tôi có thể làm điều gì đó sai, vì vậy đó là lý do tại sao tôi viết câu hỏi của tôi ở đây). Giá trị của "câu trả lời" là 1168 (ERROR_NOT_FOUND). Tôi đang sử dụng trình duyệt firefox với tư cách máy khách và tôi đã thêm chứng chỉ vào đó: Tools-> Options-> View Certificates-> Import, vì vậy firefox có lẽ nên sử dụng cert hoặc prompt cho một số chứng chỉ, nhưng tôi nghi ngờ rằng firefox doesn không nhận được yêu cầu của máy chủ cho chứng chỉ ứng dụng khách.

Lúc nào máy chủ HTTP cũng nên yêu cầu chứng chỉ ứng dụng khách? Tôi nghĩ rằng nó nên được ngay sau khi yêu cầu đến. Để chứng minh những gì chính xác tôi làm, tôi đang sử dụng HTTP Server mã ứng dụng mẫu từ Microsoft (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364640(v=vs.85).aspx), mà tôi đã sligthly chỉnh sửa:

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

Vì vậy, câu hỏi của tôi là, làm thế nào tôi có thể kích hoạt tính năng mà yêu cầu chứng chỉ ứng dụng khách và làm cách nào để xác minh chứng chỉ sau khi tôi nhận được chứng chỉ đó (mã mẫu hiện tại chỉ cố gắng nhận chứng chỉ từ ứng dụng khách, phần xác minh bị thiếu)? Tôi thực sự không tìm thấy bất kỳ mẫu nào từ internet sử dụng API Microsoft HTTP Server và yêu cầu chứng chỉ ứng dụng khách.

Cảm ơn tất cả các bạn đã có trước.

+0

Hi liismai, bạn đã thực hiện bất kỳ sự tiến bộ ? Chúc mừng, Manuel – Manuel

Trả lời

1

Các HTTP_SERVICE_CONFIG_SSL_PARAM cấu trúc, được thông qua cuối cùng để HttpSetServiceConfiguration được sử dụng để kích hoạt tính năng đàm phán của chứng chỉ khách hàng (thông qua HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT cờ) và các bước xác minh mặc định (thông qua DefaultCertCheckMode).

Bạn có thể truy xuất chứng chỉ để tiến hành xác minh bổ sung theo cách thủ công bằng cách gọi HttpReceiveClientCertificate.

Có một vài ví dụ hay, nhưng hầu hết dường như đang gọi từ .net. Cấu hình được hiển thị trong ví dụ trên p/Invoke page, xóa.overhead ròng là trái như một bài tập cho người đọc:

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

Có một separate pastebin trong đó sử dụng netsh để làm cấu hình, nhưng không truy cập vào giấy chứng nhận đã nhận:

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

} 
Các vấn đề liên quan