繁体   English   中英

Microsoft HTTP Server API - 使用SSL,如何要求客户端证书?

[英]Microsoft HTTP Server API - using SSL, how to demand client certificate?

我目前正在使用Microsoft HTTP Server API 2.0版( http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510(v=vs.85).aspx )实现一个小型HTTP服务器。

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

到目前为止,我已经能够启用服务器端SSL,因此我可以安全地连接到{ https://127.0.0.1:9999/hello }站点,向服务器发出请求并接收响应,但我无法进行打开请求客户端证书的功能(并验证它)。

我在我的应用程序代码中说我正在听“{ 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 Server代码:

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

我明白现在应该提示客户端提供证书,但它不起作用(我可能做错了,所以这就是为什么我在这里写我的问题)。 “answer”的值是1168(ERROR_NOT_FOUND)。 我正在使用firefox浏览器作为客户端,我在那里添加了一个证书:工具 - >选项 - >查看证书 - >导入,所以firefox可能应该使用该证书或提示某些证书,但我怀疑firefox没有完全没有收到服务器的客户端证书请求。

那么HTTP服务器应该在哪个位置请求客户端证书? 我认为在收到请求后应该是正确的。 为了演示我到底在做什么,我正在使用Microsoft的HTTP Server示例应用程序代码( http://msdn.microsoft.com/en-us/library/windows/desktop/aa364640(v=vs.85)。 aspx ),我已经修改了:

#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并需要客户端证书的样本。

提前谢谢大家。

最终传递给HttpSetServiceConfigurationHTTP_SERVICE_CONFIG_SSL_PARAM结构用于启用客户端证书的协商(通过HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT标志)和默认验证步骤(通过DefaultCertCheckMode )。

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

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

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

有一个单独的pastebin使用netsh进行配置,但是访问收到的证书:

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

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM