private void ProcessClientCertificate()
{
if (_clientCertState == ListenerClientCertState.InProgress)
throw new InvalidOperationException(SR.Format(SR.net_listener_callinprogress, "GetClientCertificate()/BeginGetClientCertificate()"));
_clientCertState = ListenerClientCertState.InProgress;
if (NetEventSource.IsEnabled) NetEventSource.Info(this);
//--------------------------------------------------------------------
//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;
while (true)
{
byte[] clientCertInfoBlob = new byte[checked((int)size)];
fixed (byte* pClientCertInfoBlob = clientCertInfoBlob)
{
Interop.HttpApi.HTTP_SSL_CLIENT_CERT_INFO* pClientCertInfo = (Interop.HttpApi.HTTP_SSL_CLIENT_CERT_INFO*)pClientCertInfoBlob;
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,
pClientCertInfo,
size,
&bytesReceived,
null);
if (NetEventSource.IsEnabled) NetEventSource.Info(this,
"Call to Interop.HttpApi.HttpReceiveClientCertificate returned:" + statusCode + " bytesReceived:" + bytesReceived);
if (statusCode == Interop.HttpApi.ERROR_MORE_DATA)
{
size = bytesReceived + pClientCertInfo->CertEncodedSize;
continue;
}
else if (statusCode == Interop.HttpApi.ERROR_SUCCESS)
{
if (pClientCertInfo != null)
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this,
$"pClientCertInfo:{(IntPtr)pClientCertInfo} pClientCertInfo->CertFlags: {pClientCertInfo->CertFlags} pClientCertInfo->CertEncodedSize: {pClientCertInfo->CertEncodedSize} pClientCertInfo->pCertEncoded: {(IntPtr)pClientCertInfo->pCertEncoded} pClientCertInfo->Token: {(IntPtr)pClientCertInfo->Token} pClientCertInfo->CertDeniedByMapper: {pClientCertInfo->CertDeniedByMapper}");
if (pClientCertInfo->pCertEncoded != null)
{
try
{
byte[] certEncoded = new byte[pClientCertInfo->CertEncodedSize];
Marshal.Copy((IntPtr)pClientCertInfo->pCertEncoded, certEncoded, 0, certEncoded.Length);
_clientCertificate = new X509Certificate2(certEncoded);
}
catch (CryptographicException exception)
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"CryptographicException={exception}");
}
catch (SecurityException exception)
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"SecurityException={exception}");
}
}
_clientCertificateError = (int)pClientCertInfo->CertFlags;
}
}
else
{
Debug.Assert(statusCode == Interop.HttpApi.ERROR_NOT_FOUND,
$"Call to Interop.HttpApi.HttpReceiveClientCertificate() failed with statusCode {statusCode}.");
}
}
break;
}
}
_clientCertState = ListenerClientCertState.Completed;
}