private ListenerClientCertAsyncResult AsyncProcessClientCertificate(AsyncCallback requestCallback, object state)
{
if (_clientCertState == ListenerClientCertState.InProgress)
throw new InvalidOperationException(SR.Format(SR.net_listener_callinprogress, "GetClientCertificate()/BeginGetClientCertificate()"));
_clientCertState = ListenerClientCertState.InProgress;
ListenerClientCertAsyncResult asyncResult = null;
//--------------------------------------------------------------------
//When you configure the HTTP.SYS with a flag value 2
//which means require client certificates, when the client makes the
//initial SSL connection, server (HTTP.SYS) demands the client certificate
//
//Some apps may not want to demand the client cert at the beginning
//perhaps server the default.htm. In this case the HTTP.SYS is configured
//with a flag value other than 2, whcih means that the client certificate is
//optional.So intially when SSL is established HTTP.SYS won't ask for client
//certificate. This works fine for the default.htm in the case above
//However, if the app wants to demand a client certficate at a later time
//perhaps showing "YOUR ORDERS" page, then the server wans to demand
//Client certs. this will inturn makes HTTP.SYS to do the
//SEC_I_RENOGOTIATE through which the client cert demand is made
//
//THE BUG HERE IS THAT PRIOR TO QFE 4796, we call
//GET Client certificate native API ONLY WHEN THE HTTP.SYS is configured with
//flag = 2. Which means that apps using HTTPListener will not be able to
//demand a client cert at a later point
//
//The fix here is to demand the client cert when the channel is NOT INSECURE
//which means whether the client certs are requried at the beginning or not,
//if this is an SSL connection, Call HttpReceiveClientCertificate, thus
//starting the cert negotiation at that point
//
//NOTE: WHEN CALLING THE HttpReceiveClientCertificate, you can get
//ERROR_NOT_FOUND - which means the client did not provide the cert
//If this is important, the server should respond with 403 forbidden
//HTTP.SYS will not do this for you automatically ***
//--------------------------------------------------------------------
if (_sslStatus != SslStatus.Insecure)
{
// at this point we know that DefaultFlags has the 2 bit set (Negotiate Client certificate)
// the cert, though might or might not be there. try to retrieve it
// this number is the same that IIS decided to use
uint size = CertBoblSize;
asyncResult = new ListenerClientCertAsyncResult(HttpListenerContext.RequestQueueBoundHandle, this, state, requestCallback, size);
try
{
while (true)
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Calling Interop.HttpApi.HttpReceiveClientCertificate size:" + size);
uint bytesReceived = 0;
uint statusCode =
Interop.HttpApi.HttpReceiveClientCertificate(
HttpListenerContext.RequestQueueHandle,
_connectionId,
(uint)Interop.HttpApi.HTTP_FLAGS.NONE,
asyncResult.RequestBlob,
size,
&bytesReceived,
asyncResult.NativeOverlapped);
if (NetEventSource.IsEnabled) NetEventSource.Info(this,
"Call to Interop.HttpApi.HttpReceiveClientCertificate returned:" + statusCode + " bytesReceived:" + bytesReceived);
if (statusCode == Interop.HttpApi.ERROR_MORE_DATA)
{
Interop.HttpApi.HTTP_SSL_CLIENT_CERT_INFO* pClientCertInfo = asyncResult.RequestBlob;
size = bytesReceived + pClientCertInfo->CertEncodedSize;
asyncResult.Reset(size);
continue;
}
if (statusCode != Interop.HttpApi.ERROR_SUCCESS &&
statusCode != Interop.HttpApi.ERROR_IO_PENDING)
{
// someother bad error, possible return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
// Also ERROR_BAD_DATA if we got it twice or it reported smaller size buffer required.
throw new HttpListenerException((int)statusCode);
}
if (statusCode == Interop.HttpApi.ERROR_SUCCESS &&
HttpListener.SkipIOCPCallbackOnSuccess)
{
asyncResult.IOCompleted(statusCode, bytesReceived);
}
break;
}
}
catch
{
asyncResult?.InternalCleanup();
throw;
}
}
else
{
asyncResult = new ListenerClientCertAsyncResult(HttpListenerContext.RequestQueueBoundHandle, this, state, requestCallback, 0);
asyncResult.InvokeCallback();
}
return asyncResult;
}