我目前正在使用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并需要客户端证书的互联网示例。
谢谢大家。
嗨liismai,你有没有取得任何进展?干杯,曼努埃尔 – Manuel