Quantcast
Channel: CleanCode NZ
Viewing all articles
Browse latest Browse all 28

Client Certificate Authentication – Error 403.7

$
0
0

When you implement client certificate authentication in IIS(7.5), you might get Error 403.7, and in the client side the error message looks like

The HTTP request was forbidden with client authentication scheme ‘Anonymous’.

While you have done everything according to the books, you attached a valid certificate from client side, and its Root is  indeed installed in Trusted Root Certificate Authorities on server. No matter what you do you keep getting 403.7 error from IIS.

One of the difficulties of working with certificates is the lack of logging or tracing that would be extremely helpful for trouble shooting when problem occurs. Having tried to turn on all tracing or logging in IIS that I could possibly think of, all I can get is  not a valid certificate on a seemingly good certificate.

The original client certificate authentication was meant to be used between wcf client and wcf service hosted by IIS, debugging certificate authentication in ASP.Net might prove to be too hard to do, but system diagnostics does a very good job of logging and tracing of communications on tcp  sockets, this reminds me to build SSL and client certification communications(mutual SSL) on tcp sockets which is  out side of IIS to take advantage of good tracing capability of system diagnostics on tcp.

This is proved to be the right approach, through the detailed SSL protocol traces between server and client, it did not take long for me to figure out why the certificate validation has failed.

I created two console projects one is tcp client , the other is tcp server, the tcp client creates a SSL stream using client certificate, while the server is set only accept clients with valid certificates

To load client certificates(client)

X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

store.Open(OpenFlags.OpenExistingOnly);

X509Certificate2Collection collection = store.Certificates;

X509Certificate2Collection currentCerts = collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);

X509Certificate2Collection certificates = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, "CN=tempclientcert", false);

To open a SSLStream (client)

TcpClient client = new TcpClient(hostName, port);

SslStream sslStream = new SslStream(

client.GetStream(),

false,

ValidateServerCertificate);

sslStream.AuthenticateAsClient(hostName, certificates, SslProtocols.Default, true);

sslStream.Write(messsage);

To load server certificate, this is mutual SSL

X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

store.Open(OpenFlags.OpenExistingOnly);

X509Certificate2Collection collection = store.Certificates;

X509Certificate2Collection currentCerts = collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);

X509Certificate2Collection certs = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, "CN=xxxxx", false);

foreach (var cert in certs)

{

if (cert.FriendlyName == "temp Server Cert")

{

serverCert = cert;

break;

}

}

To process client

var listener = new TcpListener(IPAddress.Any, serverPort);

while (true)

{

var client = listener.AcceptTcpClient();

}

var sslStream = new SslStream(client.GetStream(), false);

// Authenticate the server and requires the client to authenticate.

sslStream.AuthenticateAsServer(serverCert, true, SslProtocols.Default, true);

Do not forget to enable system diagnostics on both client and server


<system.diagnostics>

<trace autoflush="true" />

<sources>

<source name="System.Net">

<listeners>

<add name="System.Net"/>

</listeners>

</source>

<source name="System.Net.HttpListener">

<listeners>

<add name="System.Net"/>

</listeners>

</source>

<source name="System.Net.Sockets">

<listeners>

<add name="System.Net"/>

</listeners>

</source>

<source name="System.Net.Cache">

<listeners>

<add name="System.Net"/>

</listeners>

</source>

</sources>

<sharedListeners>

<add

name="System.Net"

type="System.Diagnostics.TextWriterTraceListener"

initializeData="System.Net.trace.log"

traceOutputOptions = "ProcessId, DateTime"

/>

</sharedListeners>

<switches>

<add name="System.Net" value="Verbose" />

<add name="System.Net.Sockets" value="Verbose" />

<add name="System.Net.Cache" value="Verbose" />

<add name="System.Net.HttpListener" value="Verbose" />

</switches>

</system.diagnostics>

With system diagnostics on you will find what is happening on every step of establishment of mutual SSL communication.

Back to the original problem, the client server communication failed to establish as expected due the client certificate authentication error, but this time I know why it is failed. In the process of client certificate authentication, the server will send a list  of trusted Root CAs to client for client to check  their client certificates against this list, if the Root CA of client certificate is in the list, then the authentication will succeed, the problem is the server only sent about 133 Root CAs, while there are about 405 Root CAs on server, so the list sent to client is not completed, truncated due to a bug mentioned here http://support.microsoft.com/kb/2801679

The solution is also provided on the above link, after run fixit, the number of my server trusted root CA dropped from 405 to 20, after this fix the same certificate passed through validation straight away.

The logging also told me how client certificate authentications works behind the scene

  1. Client ask for list of trusted root CA from server
  2. Client verifies the client certificates against root CA list
  3. Verified, then send public key of client certificate to server
  4. The server certificate must have private key on server
  5. The client certificate must have private key on client
  6. The root of CA of client certificate must be in Trusted Root Certificate Authorities

Viewing all articles
Browse latest Browse all 28

Trending Articles