2012-05-17 225 views
15

我目前正在使用Microsoft HTTP Server API Version 2.0 (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510(v=vs.85).aspx)实现一个小的HTTP服务器。Microsoft HTTP Server API - 使用SSL,如何要求客户端证书?

我需要在服务器端启用HTTPS,并且在客户端请求进入时(我需要客户端能够验证服务器和服务器以验证客户端并且它们应该通过SSL进行通信)要求客户端证书。

到目前为止,我已经能够启用服务器端SSL,因此我可以安全地连接到 {0},并向服务器发出请求并接收响应,但是我无法打开该功能也要求客户端证书(并验证它)。

我在应用程序代码中说,我在听“{} https://127.0.0.1:9999/hello” URL(这是我添加到URL组URL),然后我用Netsh.exe工具的9999端口绑定到SSL:

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

我不确定这个“clientcertnegotiation = enable”究竟应该做什么,文档说它应该“打开证书的谈判”。所以,现在我增加了一个额外的函数调用我的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); 

我了解,现在的客户会被提示输入证书,但它不工作(我可能做错事,所以这是我在这里写我的问题的原因)。 “答案”的值是1168(ERROR_NOT_FOUND)。我使用Firefox浏览器作为客户端,并且在那里添加了一个证书:工具 - >选项 - >查看证书 - >导入,因此firefox应该可能使用该证书或提示符来获取某个证书,但是我怀疑firefox doesn根本不会收到服务器对客户端证书的请求。

无论如何,HTTP服务器应该在哪个时间要求客户端证书?我认为在收到请求后应该是正确的。为了证明我究竟在做什么,我使用微软的HTTP服务器示例应用程序代码(http://msdn.microsoft.com/en-us/library/windows/desktop/aa364640(v=vs.85).aspx),我已经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; 
} 

所以我的问题是,我怎么会启用该功能需要客户端证书,我如何在收到证书后验证证书(当前示例代码只尝试从客户端接收证书,验证部分丢失)? 我真的没有找到任何使用Microsoft HTTP Server API并需要客户端证书的互联网示例。

谢谢大家。

+0

嗨liismai,你有没有取得任何进展?干杯,曼努埃尔 – Manuel

回答

1

HTTP_SERVICE_CONFIG_SSL_PARAM结构,其最终被传递到HttpSetServiceConfiguration用于启用客户端证书(通过HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT flag)和默认验证步骤(通过DefaultCertCheckMode)的协商。

您可以通过调用HttpReceiveClientCertificate来检索证书以手动执行附加验证。

有几个很好的例子,但大多数似乎是从.net调用。配置显示在p/Invoke page的示例中,删除。净开销作为练习留给读者:

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

存在使用netsh做配置的separate pastebin,但访问接收到的证书:

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

} 
相关问题