I'm currently implementing a small HTTP server using Microsoft HTTP Server API Version 2.0 (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510(v=vs.85).aspx).
I need to enable HTTPS on server side and also demand client certificate when client requests are coming in ( I need the client to be able to authenticate the server and the server to authenticate the client and they should communicate over SSL).
So far I've been able to enable server-side SSL, so I can connect securely to {https://127.0.0.1:9999/hello} site, make requests to server and receive responses, but I haven't been able to turn on the feature that requests client certificate as well (and verifys it).
I said in my application code that I'm listening "{https://127.0.0.1:9999/hello}" URL (this was the URL I added to URL group) and then I used netsh.exe tool to bind the 9999 port to SSL:
C:\>netsh http add sslcert ipport=0.0.0.0:9999 certhash=e515b6512e92f4663252eac72c28a784f2d78c6 appid={2C565242-B238-11D3-442D-0008C779D776} clientcertnegotiation=enable
I'm not sure what this "clientcertnegotiation=enable" should exactly do, the docs said it should "turn on negotiation of certificate". So now I added one additional function call to my HTTP Server code:
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 );
I understood that now the client should be prompted for certificate, but it does not work (I'm probably doing something wrong, so that is the reason why I'm writing my question here). The value of "answer" is 1168 (ERROR_NOT_FOUND). I'm using firefox browser as a client and I have added a certificate there: Tools->Options->View Certificates->Import, so firefox should probably use that cert or prompt for some certificate perhaps, but i suspect that the firefox doesn't receive the server's request for client certificate at all.
At which point should the HTTP server to ask for client certificate anyway? I thought it should be right after incoming request. To demonstrate what exactly am I doing, I'm using the HTTP Server Sample Application code from Microsoft (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364640(v=vs.85).aspx), that I've sligthly modified:
#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;
}
So my question is, how could I enable the feature that requires client certificate and how do I verify the certificate once I've received that (the current sample code only tries to receive the certificate from the client, the verification part is missing)? I really haven't found any samples from internet that use Microsoft HTTP Server API and require client certificates.
Thank you all already in advance.
Install the Certificate. cer certificate in your Trusted Root Certification Authorities for the Local Machine store using MMC (right-click over the Trusted Root Certification Authorities folder | All Tasks | Import). Install the ClientCert. pfx certificate in the Personal store of Local Computer using MMC.
Generate a client SSL certificate Generate a private key for the SSL client. Use the client's private key to generate a cert request. Issue the client certificate using the cert request and the CA cert/key. Convert the client certificate and private key to pkcs#12 format for use by browsers.
You need to send the client certificate during the TLS handshake before anything HTTP (methods, headers, URLs, request bodies) is available to be influenced. The server will not accept a client certificate sent later.
The HTTP_SERVICE_CONFIG_SSL_PARAM
structure, which is passed ultimately to HttpSetServiceConfiguration
is used to enable negotiation of client certificates (via the HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT
flag) and default verification steps (via DefaultCertCheckMode
).
You can retrieve the certificate to manually conduct additional verification by calling HttpReceiveClientCertificate
.
There are a couple good-ish examples, but most seem to be calling from .net. Configuration is shown in an example on the p/Invoke page, removal of .net overhead is left as an exercise for the reader:
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);
}
}
There is a separate pastebin which uses netsh
to do configuration, but does access the received certificate:
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 you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With